TODO List

Todo List Tutorial #

This example walks you through a hypothetical project, building a todo list.

It uses a todo list because this is well-understood application, so you can focus on the go-swagger pieces. In this example we build a server and a client.

To create your application start with swagger init:

swagger init spec \
  --title "A Todo list application" \
  --description "From the todo list tutorial on goswagger.io" \
  --version 1.0.0 \
  --scheme http \
  --consumes application/io.goswagger.examples.todo-list.v1+json \
  --produces application/io.goswagger.examples.todo-list.v1+json

This gives you a skeleton swagger.yml file:

definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean

In this model definition we say that the model item is an object with a required property description. This item model has 3 properties: id, description, and completed. The id property is an int64 value and is marked as readOnly, meaning that it will be provided by the API server and it will be ignored when the item is created.

This document also says that the description must be at least 1 char long, which results in a string property that’s not a pointer.

At this moment you have enough so that actual code could be generated, but let’s continue defining the rest of the API so that the code generation will be more useful. Now that you have a model so you can add some endpoints to list the todo’s:

paths:
  /:
    get:
      tags:
        - todos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"

With this new version of the operation you now have query params. These parameters have defaults so users can leave them off and the API will still function as intended.

However, this definition is extremely optimistic and only defines a response for the “happy path”. It’s very likely that the API will need to return errors too. That means you have to define a model errors, as well as at least one more response definition to cover the error response.

The error definition looks like this:

paths:
  /:
    get:
      tags:
        - todos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: generic error response
          schema:
            $ref: "#/definitions/error"

At this point you’ve defined your first endpoint completely. To improve the strength of this contract you could define responses for each of the status codes and perhaps return different error messages for different statuses. For now, the status code will be provided in the error message.

Try validating the specification again with swagger validate ./swagger.yml to ensure that code generation will work as expected. Generating code from an invalid specification leads to unpredictable results.

Your completed spec should look like this:

paths:
  /:
    get:
      tags:
        - todos
      operationId: find_todos
      ...

These operationId values are used to name the generated files:

.
├── cmd
│   └── todo-list-server
│       └── main.go
├── models
│   ├── error.go
│   ├── find_todos_okbody.go
│   ├── get_okbody.go
│   └── item.go
├── restapi
│   ├── configure_todo_list.go
│   ├── doc.go
│   ├── embedded_spec.go
│   ├── operations
│   │   ├── todo_list_api.go
│   │   └── todos
│   │       ├── find_todos.go
│   │       ├── find_todos_parameters.go
│   │       ├── find_todos_responses.go
│   │       └── find_todos_urlbuilder.go
│   └── server.go
└── swagger.yml

You can see that the files under restapi/operations/todos now use the operationId as part of the generated file names.

At this point can start the server, but first let’s see what --help gives you. First install the server binary and then run it:

± ~/go/src/.../examples/tutorials/todo-list/server-1
» go install ./cmd/todo-list-server/
± ~/go/src/.../examples/tutorials/todo-list/server-1
» todo-list-server --help
Usage:
  todo-list-server [OPTIONS]

From the todo list tutorial on goswagger.io

Application Options:
      --scheme=            the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec
      --cleanup-timeout=   grace period for which to wait before shutting down the server (default: 10s)
      --max-header-size=   controls the maximum number of bytes the server will read parsing the request header's keys and values, including the
                           request line. It does not limit the size of the request body. (default: 1MiB)
      --socket-path=       the unix socket to listen on (default: /var/run/todo-list.sock)
      --host=              the IP to listen on (default: localhost) [$HOST]
      --port=              the port to listen on for insecure connections, defaults to a random value [$PORT]
      --listen-limit=      limit the number of outstanding requests
      --keep-alive=        sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)
                           (default: 3m)
      --read-timeout=      maximum duration before timing out read of the request (default: 30s)
      --write-timeout=     maximum duration before timing out write of the response (default: 60s)
      --tls-host=          the IP to listen on for tls, when not specified it's the same as --host [$TLS_HOST]
      --tls-port=          the port to listen on for secure connections, defaults to a random value [$TLS_PORT]
      --tls-certificate=   the certificate to use for secure connections [$TLS_CERTIFICATE]
      --tls-key=           the private key to use for secure connections [$TLS_PRIVATE_KEY]
      --tls-ca=            the certificate authority file to be used with mutual tls auth [$TLS_CA_CERTIFICATE]
      --tls-listen-limit=  limit the number of outstanding requests
      --tls-keep-alive=    sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)
      --tls-read-timeout=  maximum duration before timing out read of the request
      --tls-write-timeout= maximum duration before timing out write of the response

Help Options:
  -h, --help               Show this help message

If you run your application now it will start on a random port by default. This might not be what you want, so you can configure a port through a command line argument or a PORT env var.

git:(master) ✗ !? » todo-list-server
serving todo list at http://127.0.0.1:64637

You can use curl to check your API:

git:(master) ✗ !? » curl -i http://127.0.0.1:64637/
HTTP/1.1 501 Not Implemented
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Thu, 31 Dec 2015 22:42:10 GMT
Content-Length: 57

"operation todos.FindTodos has not yet been implemented"

As you can see, the generated API isn’t very usable yet, but we know it runs and does something. To make it useful you’ll need to implement the actual logic behind those endpoints. And you’ll also want to add some more endpoints, like adding a new todo item and updating an existing item to change its description or mark it completed.

To supporting adding a todo item you should define a POST operation:

paths:
  /{id}:
    delete:
      tags:
        - todos
      operationId: destroyOne
      parameters:
        - type: integer
          format: int64
          name: id
          in: path
          required: true
      responses:
        204:
          description: Deleted
        default:
          description: error
          schema:
            $ref: "#/definitions/error"

This time you’re defining a parameter that is part of the path. This operation will look in the URI templated path for an id. Since there’s nothing to return after a delete, the success response is 204 No Content.

Finally, you need to define a way to update an existing item:

swagger: "2.0"
info:
  description: From the todo list tutorial on goswagger.io
  title: A Todo list application
  version: 1.0.0
consumes:
- application/io.goswagger.examples.todo-list.v1+json
produces:
- application/io.goswagger.examples.todo-list.v1+json
schemes:
- http
- https
paths:
  /:
    get:
      tags:
        - todos
      operationId: findTodos
      parameters:
        - name: since
          in: query
          type: integer
          format: int64
        - name: limit
          in: query
          type: integer
          format: int32
          default: 20
      responses:
        200:
          description: list the todo operations
          schema:
            type: array
            items:
              $ref: "#/definitions/item"
        default:
          description: generic error response
          schema:
            $ref: "#/definitions/error"
    post:
      tags:
        - todos
      operationId: addOne
      parameters:
        - name: body
          in: body
          schema:
            $ref: "#/definitions/item"
      responses:
        201:
          description: Created
          schema:
            $ref: "#/definitions/item"
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
  /{id}:
    parameters:
      - type: integer
        format: int64
        name: id
        in: path
        required: true
    put:
      tags:
        - todos
      operationId: updateOne
      parameters:
        - name: body
          in: body
          schema:
            $ref: "#/definitions/item"
      responses:
        200:
          description: OK
          schema:
            $ref: "#/definitions/item"
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
    delete:
      tags:
        - todos
      operationId: destroyOne
      responses:
        204:
          description: Deleted
        default:
          description: error
          schema:
            $ref: "#/definitions/error"
definitions:
  item:
    type: object
    required:
      - description
    properties:
      id:
        type: integer
        format: int64
        readOnly: true
      description:
        type: string
        minLength: 1
      completed:
        type: boolean
  error:
    type: object
    required:
      - message
    properties:
      code:
        type: integer
        format: int64
      message:
        type: string

This is a good time to sanity check and by validating the schema:

± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-2
git:(master) ✗ !? » swagger validate ./swagger.yml
The swagger spec at "./swagger.yml" is valid against swagger specification 2.0

Now you’re ready to generate the API and start filling in the actual operations:

git:(master) ✗ !? » swagger generate server -A TodoList -f ./swagger.yml
... elided output ...
2015/12/31 18:16:28 rendered main template: server.TodoList
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-2
git:(master) ✗ !? » tree
.
├── cmd
│   └── todo-list-server
│       └── main.go
├── models
│   ├── error.go
│   ├── find_todos_okbody.go
│   └── item.go
├── restapi
│   ├── configure_todo_list.go
│   ├── doc.go
│   ├── embedded_spec.go
│   ├── operations
│   │   ├── todo_list_api.go
│   │   └── todos
│   │       ├── add_one.go
│   │       ├── add_one_parameters.go
│   │       ├── add_one_responses.go
│   │       ├── add_one_urlbuilder.go
│   │       ├── destroy_one.go
│   │       ├── destroy_one_parameters.go
│   │       ├── destroy_one_responses.go
│   │       ├── destroy_one_urlbuilder.go
│   │       ├── find_todos.go
│   │       ├── find_todos_parameters.go
│   │       ├── find_todos_responses.go
│   │       ├── find_todos_urlbuilder.go
│   │       ├── update_one.go
│   │       ├── update_one_parameters.go
│   │       ├── update_one_responses.go
│   │       └── update_one_urlbuilder.go
│   └── server.go
└── swagger.yml

6 directories, 26 files

To implement the core of your application you start by editing restapi/configure_todo_list.go. This file is safe to edit. Its content will not be overwritten if you run swagger generate again the future.

The simplest way to implement this application is to simply store all the todo items in a golang map. This provides a simple way to move forward without bringing in complications like a database or files.

To do this you’ll need a map and a counter to track the last assigned id:

// the variables we need throughout our implementation
var items = make(map[int64]*models.Item)
var lastID int64

The simplest handler to implement now is the delete handler. Because the store is a map and the id of the item is provided in the request it’s a one liner.

api.TodosDestroyOneHandler = todos.DestroyOneHandlerFunc(func(params todos.DestroyOneParams) middleware.Responder {
  delete(items, params.ID)
  return todos.NewDestroyOneNoContent()
})

After deleting the item from the store, you need to provide a response. The code generator created responders for each response you defined in the swagger specification, and you can see how one of those is being used in the example above.

The other 3 handler implementations are similar to this one. They are provided in the source for this tutorial.

So assuming you go ahead and implement the remainder of the endpoints, you’re all set to test it out:

» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:01 GMT
Content-Length: 3

[]
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}"
HTTP/1.1 415 Unsupported Media Type
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:11 GMT
Content-Length: 157

{"code":415,"message":"unsupported media type \"application/x-www-form-urlencoded\", only [application/io.goswagger.examples.todo-list.v1+json] are allowed"}                                                                                                     ± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 201 Created
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:20 GMT
Content-Length: 39

{"description":"message 30925","id":1}
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 201 Created
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:23 GMT
Content-Length: 37

{"description":"message 104","id":2}
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765 -d "{\"description\":\"message $RANDOM\"}" -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 201 Created
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:24 GMT
Content-Length: 39

{"description":"message 15225","id":3}
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:26 GMT
Content-Length: 117

[{"description":"message 30925","id":1},{"description":"message 104","id":2},{"description":"message 15225","id":3}]
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765/3 -X PUT -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json' -d '{"description":"go shopping"}'
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:32 GMT
Content-Length: 37

{"description":"go shopping","id":3}
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:56:34 GMT
Content-Length: 115

[{"description":"message 30925","id":1},{"description":"message 104","id":2},{"description":"go shopping","id":3}]
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765/1 -X DELETE -H 'Content-Type: application/io.goswagger.examples.todo-list.v1+json'
HTTP/1.1 204 No Content
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:57:04 GMT
± ~/go/src/github.com/go-swagger/go-swagger/examples/tutorials/todo-list/server-complete
» curl -i localhost:8765
HTTP/1.1 200 OK
Content-Type: application/io.goswagger.examples.todo-list.v1+json
Date: Fri, 01 Jan 2016 19:57:06 GMT
Content-Length: 76

[{"description":"message 104","id":2},{"description":"go shopping","id":3}]

Copyright 2015-2024 the go-swagger maintainers. This documentation is under an Apache 2.0 license.

Site built with HUGO using hugo-book