Codebase list golang-github-manyminds-api2go / HEAD
HEAD

Tree @HEAD (Download .tar.gz)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
# api2go

[![Join the chat at https://gitter.im/manyminds/api2go](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/manyminds/api2go?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![GoDoc](https://godoc.org/github.com/manyminds/api2go?status.svg)](https://godoc.org/github.com/manyminds/api2go)
[![Build Status](https://travis-ci.org/manyminds/api2go.svg?branch=master)](https://travis-ci.org/manyminds/api2go)
[![Coverage Status](https://coveralls.io/repos/github/manyminds/api2go/badge.svg?branch=master)](https://coveralls.io/github/manyminds/api2go?branch=master)
[![Go Report Card](https://goreportcard.com/badge/manyminds/api2go)](https://goreportcard.com/report/manyminds/api2go)

A [JSON API](http://jsonapi.org) Implementation for Go, to be used e.g. as server for [Ember Data](https://github.com/emberjs/data).

## TOC
- [Installation](#installation)
- [Basic functionality](#basic-functionality)
- [Examples](#examples)
- [Interfaces to implement](#interfaces-to-implement)
  - [Responder](#responder)
  - [EntityNamer](#entitynamer)
  - [MarshalIdentifier](#marshalidentifier)
  - [UnmarshalIdentifier](#unmarshalidentifier)
  - [Marshalling with References to other structs](#marshalling-with-references-to-other-structs)
  - [Unmarshalling with references to other structs](#unmarshalling-with-references-to-other-structs)
- [Manual marshalling / unmarshalling](#manual-marshalling--unmarshalling)
- [SQL Null-Types](#sql-null-types)
- [Using api2go with the gin framework](#using-api2go-with-the-gin-framework)
- [Building a REST API](#building-a-rest-api)
  - [Query Params](#query-params)
  - [Using Pagination](#using-pagination)
  - [Fetching related IDs](#fetching-related-ids)
  - [Fetching related resources](#fetching-related-resources)
  - [Using middleware](#using-middleware)
  - [Dynamic URL Handling](#dynamic-url-handling)
- [Tests](#tests)

# Installation

For the complete api2go package use:
```go
go get github.com/manyminds/api2go
```

If you only need marshalling and/or unmarshalling:
```
go get github.com/manyminds/api2go/jsonapi 
```

## Basic functionality
Api2go will Marshal/Unmarshal exactly like the internal `json` package from Go
with one addition: It will decorate the Marshalled json with jsonapi meta
objects. Jsonapi wraps the payload inside an `attributes` object. The rest is
just Meta-Data which will be generated by api2go.

So let's take this basic example:

```go
type Article struct {
	ID    string
	Title string `json:"title"`
}
```

Would `json.Marshal` into this Json:

```json
{
  "ID": "Some-ID",
  "title": "the title"
}
```

For api2go, you have to ignore tag the `ID` field and then the result could be
something like this:

```json
{
  "type": "articles",
  "id": "1",
  "attributes": {
    "title": "Rails is Omakase"
  },
  "relationships": {
    "author": {
      "links": {
        "self": "/articles/1/relationships/author",
        "related": "/articles/1/author"
      },
      "data": { "type": "people", "id": "9" }
    }
  }
}
```

All the additional information is retrieved by implementing some interfaces.

## Examples

- Basic Examples can be found [here](https://github.com/manyminds/api2go/blob/master/examples/crud_example.go).
- For a more real life example implementation of api2go using [jinzhu/gorm](https://github.com/jinzhu/gorm) and [gin-gonic/gin](https://github.com/gin-gonic/gin) you can have a look at hnakamur's [repository](https://github.com/hnakamur/api2go-gorm-gin-crud-example)

## Interfaces to implement
For the following query and result examples, imagine the following 2 structs which represent a posts and
comments that belong with a has-many relation to the post.

```go
type Post struct {
  ID          int       `json:"-"`  // Ignore ID field because the ID is fetched via the
                                    // GetID() method and must not be inside the attributes object.
  Title       string    `json:"title"`
  Comments    []Comment `json:"-"` // this will be ignored by the api2go marshaller
  CommentsIDs []int     `json:"-"` // it's only useful for our internal relationship handling
}

type Comment struct {
  ID   int    `json:"-"`
  Text string `json:"text"`
}
```

You must at least implement the [MarshalIdentifier](#marshalidentifier) interface, which is the one for marshalling/unmarshalling the primary `ID` of the struct
that you want to marshal/unmarshal. This is because of the huge variety of types that you could  use for the primary ID. For example a string,
a UUID or a BSON Object for MongoDB etc...

In the Post example struct, the `ID` field is ignored because api2go will use the `GetID` method that you implemented 
for your struct to fetch the ID of the struct.
Every field inside a struct will be marshalled into the `attributes` object in
the json. In our example, we just want to have the `Title` field there.

Don't forget to name all your fields with the `json:"yourName"` tag. 

### Responder
```go
type Responder interface {
	Metadata() map[string]interface{}
	Result() interface{}
	StatusCode() int
}
```

The Responder interface must be implemented if you are using our API. It
contains everything that is needed for a response. You can see an example usage
of it in our example project.

### EntityNamer
```go
type EntityNamer interface {
	GetName() string
}
```

EntityNamer is an optional interface. Normally, the name of
a struct will be automatically generated in its plural form. For example if
your struct has the type `Post`, its generated name is `posts`. And the url
for the GET request for post with ID 1 would be `/posts/1`.

If you implement the `GetName()` method and it returns `special-posts`, then
this would be the name in the `type` field of the generated json and also the
name for the generated routes.

Currently, you must implement this interface, if you have a struct type that
consists of multiple words and you want to use a **hyphenized** name. For example `UnicornPost`.
Our default Jsonifier would then generate the name `unicornPosts`. But if you
want the [recommended](http://jsonapi.org/recommendations/#naming) name, you
have to implement `GetName`

```go
func (s UnicornPost) GetName() string {
	return "unicorn-posts"
}
```

### MarshalIdentifier
```go
type MarshalIdentifier interface {
	GetID() string
}
```

Implement this interface to marshal a struct.

### UnmarshalIdentifier
```go
type UnmarshalIdentifier interface {
	SetID(string) error
}
```

This is the corresponding interface to MarshalIdentifier. Implement this interface in order to unmarshal incoming json into
a struct.

### Marshalling with References to other structs
For relationships to work, there are 3 Interfaces that you can use:

```go
type MarshalReferences interface {
	GetReferences() []Reference
}

// MarshalLinkedRelations must be implemented if there are references and the reference IDs should be included
type MarshalLinkedRelations interface {
	MarshalReferences
	MarshalIdentifier
	GetReferencedIDs() []ReferenceID
}

// MarshalIncludedRelations must be implemented if referenced structs should be included
type MarshalIncludedRelations interface {
	MarshalReferences
	MarshalIdentifier
	GetReferencedStructs() []MarshalIdentifier
}
```

Implementing those interfaces is not mandatory and depends on your use cases. If your API has any relationships, 
you must at least implement `MarshalReferences` and `MarshalLinkedRelations`.

`MarshalReferences` must be implemented in order for api2go to know which relations are possible for your struct.

`MarshalLinkedRelations` must be implemented to retrieve the `IDs` of the relations that are connected to this struct. This method
could also return an empty array, if there are currently no relations. This is why there is the `MarshalReferences` interface, so that api2go
knows what is possible, even if nothing is referenced at the time.

In addition to that, you can implement `MarshalIncludedRelations` which exports the complete referenced structs and embeds them in the json
result inside the `included` object.

**That way you can choose how you internally manage relations.** So, there are no limits regarding the use of ORMs.

### Unmarshalling with references to other structs
Incoming jsons can also contain reference IDs. In order to unmarshal them correctly, you have to implement the following interfaces. If you only have to-one
relationships, the `UnmarshalToOneRelations` interface is enough. 

```go
// UnmarshalToOneRelations must be implemented to unmarshal to-one relations
type UnmarshalToOneRelations interface {
	SetToOneReferenceID(name, ID string) error
}

// UnmarshalToManyRelations must be implemented to unmarshal to-many relations
type UnmarshalToManyRelations interface {
	SetToManyReferenceIDs(name string, IDs []string) error
}
```

**If you need to know more about how to use the interfaces, look at our tests or at the example project.**

## Manual marshalling / unmarshalling
Please keep in mind that this only works if you implemented the previously mentioned interfaces. Manual marshalling and
unmarshalling makes sense, if you do not want to use our API that automatically generates all the necessary routes for you. You
can directly use our sub-package `github.com/manyminds/api2go/jsonapi` 

```go
comment1 = Comment{ID: 1, Text: "First!"}
comment2 = Comment{ID: 2, Text: "Second!"}
post = Post{ID: 1, Title: "Foobar", Comments: []Comment{comment1, comment2}}

json, err := jsonapi.Marshal(post)
```

will yield

```json
{
  "data": [
    {
      "id": "1",
      "type": "posts",
      "attributes": {
        "title": "Foobar"
      },
      "relationships": {
        "comments": {
          "data": [
            {
              "id": "1",
              "type": "comments"
            },
            {
              "id": "2",
              "type": "comments"
            }
          ]
        }
      }
    }
  ],
  "included": [
    {
      "id": "1",
      "type": "comments",
      "attributes": {
        "text": "First!"
      }
    },
    {
      "id": "2",
      "type": "comments",
      "attributes": {
        "text": "Second!"
      }
    }
  ]
}
```

You can also use `jsonapi.MarshalWithURLs` to automatically generate URLs for the rest endpoints that have a
version and BaseURL prefix. This will generate the same routes that our API uses. This adds `self` and `related` fields
for relations inside the `relationships` object.

Recover the structure from above using. Keep in mind that Unmarshalling with
included structs does not work yet. So Api2go cannot be used as a client yet.

```go
var posts []Post
err := jsonapi.Unmarshal(json, &posts)
// posts[0] == Post{ID: 1, Title: "Foobar", CommentsIDs: []int{1, 2}}
```
## SQL Null-Types
When using a SQL Database it is most likely you want to use the special SQL-Types from the `database/sql` package. These are

- sql.NullBool
- sql.NullFloat64
- sql.NullInt64
- sql.NullString

The Problem is, that they internally manage the `null` value behavior by using a custom struct. In order to Marshal und Unmarshal
these values, it is required to implement the `json.Marshaller` and `json.Unmarshaller` interfaces of the go standard library.

But you dont have to do this by yourself! There already is a library that did the work for you. We recommend that you use the types
of this library: http://gopkg.in/guregu/null.v2/zero

In order to use omitempty with those types, you need to specify them as pointers in your struct.

## Using api2go with the gin framework

If you want to use api2go with [gin](https://github.com/gin-gonic/gin) you need to use a different router than the default one.
Get the according adapter using:

```go get -tags=gingonic github.com/manyminds/api2go```

Currently the supported tags are: `gingonic`,`gorillamux`, or `echo`.

After that you can bootstrap api2go the following way:
```go
  import (
    "github.com/gin-gonic/gin"
    "github.com/manyminds/api2go"
    "github.com/manyminds/api2go/routing"
    "github.com/manyminds/api2go/examples/model"
    "github.com/manyminds/api2go/examples/resource"
    "github.com/manyminds/api2go/examples/storage"
  )

  func main() {
    r := gin.Default()
    api := api2go.NewAPIWithRouting(
      "api",
      api2go.NewStaticResolver("/"),
      routing.Gin(r),
    )

    userStorage := storage.NewUserStorage()
    chocStorage := storage.NewChocolateStorage()
    api.AddResource(model.User{}, resource.UserResource{ChocStorage: chocStorage, UserStorage: userStorage})
    api.AddResource(model.Chocolate{}, resource.ChocolateResource{ChocStorage: chocStorage, UserStorage: userStorage})

    r.GET("/ping", func(c *gin.Context) {
      c.String(200, "pong")
    })
    r.Run(":8080")
  }
```

Keep in mind that you absolutely should map api2go under its own namespace to not get conflicts with your normal routes.

If you need api2go with any different go framework, just send a PR with the according adapter :-)

## Building a REST API

First, write an implementation of either `api2go.ResourceGetter`, `api2go.ResourceCreator`, `api2go.ResourceUpdater`,  `api2go.ResourceDeleter`, or any combination of them.
You can also write an implementation the `CRUD` interface which embed all of them.
You have to implement at least one of these 4 methods:

```go
type fixtureSource struct {}

// FindOne returns an object by its ID
// Possible success status code 200
func (s *fixtureSource) FindOne(ID string, r api2go.Request) (Responder, error) {}

// Create a new object. Newly created object/struct must be in Responder.
// Possible status codes are:
// - 201 Created: Resource was created and needs to be returned
// - 202 Accepted: Processing is delayed, return nothing
// - 204 No Content: Resource created with a client generated ID, and no fields were modified by
//   the server
func (s *fixtureSource) Create(obj interface{}, r api2go.Request) (Responder, err error) {}

// Delete an object
// Possible status codes are:
// - 200 OK: Deletion was a success, returns meta information, currently not implemented! Do not use this
// - 202 Accepted: Processing is delayed, return nothing
// - 204 No Content: Deletion was successful, return nothing
func (s *fixtureSource) Delete(id string, r api2go.Request) (Responder, err error) {}

// Update an object
// Possible status codes are:
// - 200 OK: Update successful, however some field(s) were changed, returns updates source
// - 202 Accepted: Processing is delayed, return nothing
// - 204 No Content: Update was successful, no fields were changed by the server, return nothing
func (s *fixtureSource) Update(obj interface{}, r api2go.Request) (Responder, err error) {}
```

If you want to return a jsonapi compatible error because something went wrong inside the CRUD methods, you can use our
`HTTPError` struct, which can be created with `NewHTTPError`. This allows you to set the error status code and add
as many information about the error as you like. See: [jsonapi error](http://jsonapi.org/format/#errors)

To fetch all objects of a specific resource you can choose to implement one or both of the following
interfaces:

```go
type FindAll interface {
	// FindAll returns all objects
	FindAll(req Request) (Responder, error)
}

type PaginatedFindAll interface {
	PaginatedFindAll(req Request) (totalCount uint, response Responder, err error)
}
```

`FindAll` returns everything. You could limit the results only by using Query Params which are described [here](#query-params)

`PaginatedFindAll` can also use Query Params, but in addition to that it does not need to send all objects at once and can split
up the result with pagination. You have to return the total number of found objects in order to let our API automatically generate
pagination links. More about pagination is described [here](#using-pagination)

You can then create an API:

```go
api := api2go.NewAPI("v1")
api.AddResource(Post{}, &PostsSource{})
http.ListenAndServe(":8080", api.Handler())
```

Instead of `api2go.NewAPI` you can also use `api2go.NewAPIWithBaseURL("v1", "http://yourdomain.com")` to prefix all
automatically generated routes with your domain and protocoll.

This generates the standard endpoints:

```
OPTIONS /v1/posts
OPTIONS /v1/posts/<id>
GET     /v1/posts
POST    /v1/posts
GET     /v1/posts/<id>
PATCH   /v1/posts/<id>
DELETE  /v1/posts/<id>
GET     /v1/posts/<id>/comments            // fetch referenced comments of a post
GET     /v1/posts/<id>/relationships/comments      // fetch IDs of the referenced comments only
PATCH   /v1/posts/<id>/relationships/comments      // replace all related comments

// These 2 routes are only created for to-many relations that implement EditToManyRelations interface
POST    /v1/posts/<id>/relationships/comments      // Add a new comment reference, only for to-many relations
DELETE  /v1/posts/<id>/relationships/comments      // Delete a comment reference, only for to-many relations
```

For the last two generated routes, it is necessary to implement the `jsonapi.EditToManyRelations` interface.

```go
type EditToManyRelations interface {
	AddToManyIDs(name string, IDs []string) error
	DeleteToManyIDs(name string, IDs []string) error
}
```

All PATCH, POST and DELETE routes do a `FindOne` and update the values/relations in the previously found struct. This
struct will then be passed on to the `Update` method of a resource struct. So you get all these routes "for free" and just
have to implement the `ResourceUpdater` `Update` method.

### Query Params
To support all the features mentioned in the `Fetching Resources` section of Jsonapi:
http://jsonapi.org/format/#fetching

If you want to support any parameters mentioned there, you can access them in your Resource
via the `api2go.Request` Parameter. This currently supports `QueryParams` which holds
all query parameters as `map[string][]string` unfiltered. So you can use it for:
  * Filtering
  * Inclusion of Linked Resources
  * Sparse Fieldsets
  * Sorting
  * Aything else you want to do that is not in the official Jsonapi Spec

```go
type fixtureSource struct {}

func (s *fixtureSource) FindAll(req api2go.Request) (Responder, error) {
  for key, values range req.QueryParams {
    ...
  }
  ...
}
```

If there are multiple values, you have to separate them with a comma. api2go automatically
slices the values for you.

```
Example Request
GET /people?fields=id,name,age

req.QueryParams["fields"] contains values: ["id", "name", "age"]
```

### Using Pagination
Api2go can automatically generate the required links for pagination. Currently there are 2 combinations of query
parameters supported:

- page[number], page[size]
- page[offset], page[limit]

Pagination is optional. If you want to support pagination, you have to implement the `PaginatedFindAll` method
in you resource struct. For an example, you best look into our example project.

Example request

```
GET /v0/users?page[number]=2&page[size]=2
```

would return a json with the top level links object

```json
{
  "links": {
    "first": "http://localhost:31415/v0/users?page[number]=1&page[size]=2",
    "last": "http://localhost:31415/v0/users?page[number]=5&page[size]=2",
    "next": "http://localhost:31415/v0/users?page[number]=3&page[size]=2",
    "prev": "http://localhost:31415/v0/users?page[number]=1&page[size]=2"
  },
  "data": [...]
}
```

### Fetching related IDs
The IDs of a relationship can be fetched by following the `self` link of a relationship object in the `links` object
of a result. For the posts and comments example you could use the following generated URL:

```
GET /v1/posts/1/relationships/comments
```

This would return all comments that are currently referenced by post with ID 1. For example:

```json
{
  "links": {
    "self": "/v1/posts/1/relationships/comments",
    "related": "/v1/posts/1/comments"
  },
  "data": [
    {
      "type": "comments",
      "id": "1"
    },
    {
      "type":"comments",
      "id": "2"
    }
  ]
}
```

### Fetching related resources
Api2go always creates a `related` field for elements in the `relationships` object of the result. This is like it's
specified on jsonapi.org. Post example:

```json
{
  "data": [
    {
      "id": "1",
      "type": "posts",
      "title": "Foobar",
      "relationships": {
        "comments": {
          "links": {
            "related": "/v1/posts/1/comments",
            "self": "/v1/posts/1/relationships/comments"
          },
          "data": [
            {
              "id": "1",
              "type": "comments"
            },
            {
              "id": "2",
              "type": "comments"
            }
          ]
        }
      }
    }
  ]
}
```

If a client requests this `related` url, the `FindAll` method of the comments resource will be called with a query
parameter `postsID`.

So if you implement the `FindAll` method, do not forget to check for all possible query Parameters. This means you have
to check all your other structs and if it references the one for that you are implementing `FindAll`, check for the
query Paramter and only return comments that belong to it. In this example, return the comments for the Post.

### Using middleware
We provide a custom `APIContext` with
a [context](https://godoc.org/context) implementation that you
can use if you for example need to check if a user is properly authenticated
before a request reaches the api2go routes.

You can either use our struct or implement your own with the `APIContexter`
interface

```go
type APIContexter interface {
    context.Context
    Set(key string, value interface{})
    Get(key string) (interface{}, bool)
    Reset()
}
```

If you implemented your own `APIContexter`, don't forget to define
a `APIContextAllocatorFunc` and set it with `func (api *API) SetContextAllocator(allocator APIContextAllocatorFunc)`

But in most cases, this is not needed.

To use a middleware, it is needed to implement our
`type HandlerFunc func(APIContexter, http.ResponseWriter, *http.Request)`. A `HandlerFunc` can then be 
registered with `func (api *API) UseMiddleware(middleware ...HandlerFunc)`. You can either pass one or many middlewares 
that will be executed in order before any other api2go routes. Use this to set up database connections, user authentication
and so on.

### Dynamic URL handling
If you have different TLDs for one api, or want to use different domains in development and production, you can implement a custom
URLResolver in api2go. 

There is a simple interface, which can be used if you get TLD information from the database, the server environment, or anything else
that's not request dependant:
```go
type URLResolver interface {
	GetBaseURL() string
}
```
And a more complex one that also gets request information:
```go
type RequestAwareURLResolver interface {
	URLResolver
	SetRequest(http.Request)
}
```

For most use cases we provide a CallbackResolver which works on a per request basis and may fill
your basic needs. This is particulary useful if you are using an nginx proxy which sets `X-Forwarded-For` headers.

```go
resolver := NewCallbackResolver(func(r http.Request) string{})
api := NewApiWithMarshalling("v1", resolver, marshalers)
```
## Tests

```sh
go test ./...
ginkgo -r                # Alternative
ginkgo watch -r -notify  # Watch for changes
```