Skip to content

lucas-natraj/go-notes

Repository files navigation

layout category author tags title
post
documentation
Lucas Natraj
Swagger
YAML
JSON
Generate
Go
Golang
Swagger Generation from a Go Web Application

Simple Go Web Application

Application Structure:

.   <------------------------------ GOPATH
├── bin
│   └── ...
├── pkg
│   └── ...
├── src
│   ├── ...
│   └── notes
│       ├── app
│       │   ├── app.go
│       │   └── notes.yaml
│       ├── vendor   <------------- 3rd party dependencies
│       │   └── ...
│       ├── glide.lock   <--------- Dependency versions
│       ├── glide.yaml   <--------- Dependency list
│       └── main.go
├── swagger-aux.json
└── swagger.json   <--------------- Generated Swagger

The implementation below is a fairly straightforward note management application, with the following REST-ful api -

  • POST /notes - Add a new note
  • GET /notes - Get all notes
  • GET /notes/:index - Get note at specified index
  • PUT /notes/:index - Update note at specified index
  • DELETE /notes/:index - Delete note at specified index
// -------  src/notes/app/app.go  -------

// Package app Notes Service.
//
// Notes management service
//
// Schemes: http
// Host: localhost:3000
// BasePath: /
// Version: 1.0.0
//
// Produces:
// - application/json
//
//
// swagger:meta
//
// go:generate swagger generate spec -o swagger.json
package app

import (
    "encoding/json"
    "net/http"
    "strconv"

    "github.com/gorilla/pat"
    "github.com/rs/cors"
)

// NotesService is the main service that manages the Notes
type NotesService struct {
    Notes []Note
}

// NewNotesService creates a new instance of the NotesService
func NewNotesService() *NotesService {
    t := new(NotesService)
    t.Notes = make([]Note, 0)
    return t
}

// Singleton to store all the notes
var noteService = NewNotesService()

// Info swagger:route GET /info info info-status
//
// Get service information
//
// Get service information
//
// Security:
//   my_auth: email
//
// Responses:
//     200: InfoResponse
func (ns *NotesService) Info(w http.ResponseWriter, r *http.Request) {

    info := &Info{
        Service: "Notes",
        Status:  "ok",
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(info)
}

// AddNote swagger:route POST /notes notes notes-add
//
// Add a new note
//
// Adds a new note
//
// Security:
//   my_auth: email
//
// Responses:
//     200: Success
func (ns *NotesService) AddNote(w http.ResponseWriter, r *http.Request) {
    decoder := json.NewDecoder(r.Body)
    var body AddNoteRequestBody
    err := decoder.Decode(&body)
    if err != nil {
        panic(nil)
    }

    var Note = Note{
        Title:   body.Title,
        Content: body.Content,
    }

    ns.Notes = append(noteService.Notes, Note)
}

// FetchAllNotes swagger:route GET /notes notes notes-fetchAll
//
// Fetches all the notes
//
// Returns the entire list of notes
//
// Security:
//   my_auth: email
//
// Responses:
//     200: FetchAllNotesResponse
func (ns *NotesService) FetchAllNotes(w http.ResponseWriter, r *http.Request) {

    body := &FetchAllNotesResponseBody{
        Notes: ns.Notes,
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(body)
}

// GetNote swagger:route GET /notes/{id} notes notes-fetchOne
//
// Fetch a single note
//
// Fetch a single note by index
//
// Security:
//   my_auth: email
//
// Responses:
//     200: FetchNoteResponse
func (ns *NotesService) GetNote(w http.ResponseWriter, r *http.Request) {

    params := r.URL.Query()
    id := params.Get(":id")
    if len(id) == 0 {
        http.Error(w, "missing note id", http.StatusBadRequest)
        return
    }

    index, err := strconv.Atoi(id)
    if err != nil || (index < 0 || index >= len(ns.Notes)) {
        http.Error(w, "invalid note id", http.StatusBadRequest)
        return
    }

    body := &FetchNoteResponse{
        Body: &Note{
            Title:   ns.Notes[index].Title,
            Content: ns.Notes[index].Content,
        },
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(body)
}

// RemoveNote swagger:route DELETE /notes/{id} notes notes-remove
//
// Removes a single note
//
// Removes a single note by index
//
// Security:
//   my_auth: email
//
// Responses:
//     200: Success
func (ns *NotesService) RemoveNote(w http.ResponseWriter, r *http.Request) {

    params := r.URL.Query()
    id := params.Get(":id")
    if len(id) == 0 {
        http.Error(w, "missing note id", http.StatusBadRequest)
        return
    }

    index, err := strconv.Atoi(id)
    if err != nil || (index < 0 || index >= len(ns.Notes)) {
        http.Error(w, "invalid note id", http.StatusBadRequest)
        return
    }

    ns.Notes = append(ns.Notes[:index], ns.Notes[index+1:]...)
}

// UpdateNote swagger:route PUT /notes/{id} notes notes-update
//
// Updates a single note
//
// Updates the note at the specified index
//
// Security:
//   my_auth: email
//
// Responses:
//     200: Success
func (ns *NotesService) UpdateNote(w http.ResponseWriter, r *http.Request) {

    params := r.URL.Query()
    id := params.Get(":id")
    if len(id) == 0 {
        http.Error(w, "missing note id", http.StatusBadRequest)
        return
    }

    index, err := strconv.Atoi(id)
    if err != nil || (index < 0 || index >= len(ns.Notes)) {
        http.Error(w, "invalid note id", http.StatusBadRequest)
        return
    }

    decoder := json.NewDecoder(r.Body)
    var body AddNoteRequestBody
    err = decoder.Decode(&body)
    if err != nil {
        panic(nil)
    }

    ns.Notes[index] = Note{
        Title:   body.Title,
        Content: body.Content,
    }
}

// initialize the identity service endpoints
func init() {
    r := pat.New()
    r.Get("/info", noteService.Info)
    r.Get("/notes/{id}", noteService.GetNote)
    r.Delete("/notes/{id}", noteService.RemoveNote)
    r.Put("/notes/{id}", noteService.UpdateNote)
    r.Get("/notes", noteService.FetchAllNotes)
    r.Post("/notes", noteService.AddNote)

    c := cors.New(cors.Options{
        AllowedHeaders: []string{"*"},
        AllowedMethods: []string{"GET", "POST", "DELETE", "PUT"},
    })
    handler := c.Handler(r)
    http.Handle("/", handler)
}
// -------  src/notes/app/messages.go  -------

package app

// Note is the persistence format of a note
type Note struct {
    Title   string `json:"title"`
    Content string `json:"content"`
}

// Info is the response format for the Info request
type Info struct {
    Service string `json:"service"`
    Status  string `json:"status"`
}

// InfoResponse is the response to the Info request
//
// swagger:response
type InfoResponse struct {
    // required: true
    // in: body
    Body *Info `json:"body"`
}

// AddNoteRequest is the request structure
//
// swagger:parameters notes-add
type AddNoteRequest struct {
    // Payload
    //
    // required: true
    // in: body
    Body *AddNoteRequestBody
}

// AddNoteRequestBody is the body of the AddNoteRequest request
//
// swagger:model
type AddNoteRequestBody struct {
    Title   string `json:"title" binding:"required"`
    Content string `json:"content" binding:"required"`
}

// FetchAllNotesResponse is the response containing the list of Notes
//
// swagger:response
type FetchAllNotesResponse struct {
    // required: true
    // in: body
    Notes *FetchAllNotesResponseBody `json:"notes"`
}

// FetchAllNotesResponseBody is the body of the FetchAllNotesResponse response
type FetchAllNotesResponseBody struct {
    // The Note List
    Notes []Note `json:"notes"`
}

// FetchNoteRequest is the request for the FetchNote request
// swagger:parameters notes-fetchOne
type FetchNoteRequest struct {
    // Index of Note
    //
    // required: true
    // in: path
    // minimum: 0
    // default: 0
    NoteID int `json:"id" binding:"required"`
}

// FetchNoteResponse is the response when fetching a specific note
//
// swagger:response
type FetchNoteResponse struct {
    // required: true
    // in: body
    Body *Note `json:"body"`
}

// DeleteNoteRequest is the request for deleting a note
//
// swagger:parameters notes-remove
type DeleteNoteRequest struct {
    // Index of the note to be removed
    //
    // required: true
    // in: path
    // minimum: 0
    // default: 0
    NoteID int `json:"id" binding:"required"`
}

// UpdateNoteRequest is the request for updating a specific note
//
// swagger:parameters notes-update
type UpdateNoteRequest struct {
    // Index of Note to be updated
    //
    // required: true
    // in: path
    // minimum: 0
    // default: 0
    NoteID int `json:"id" binding:"required"`

    // Payload
    //
    // required: true
    // in: body
    Body *AddNoteRequestBody
}

// Success is the response for a successful request
//
// swagger:response
type Success struct {
}

Providing security definitions is done via an auxilliary file that is merged in during the spec generation process.

------- swagger-aux.json -------

{
  "securityDefinitions": {
    "my_auth": {
      "type": "oauth2",
      "flow": "implicit",
      "authorizationUrl": "https://accounts.google.com/o/oauth2/v2/auth",
      "scopes": {
        "email": "default scope"
      }
    }
  }
}

Running Swagger UI Locally

Instructions at https://github.com/swagger-api/swagger-ui

  • To run the Swagger Editor locally using docker:

    docker pull swaggerapi/swagger-editor
    docker run -d -p 80:8080 swaggerapi/swagger-editor

    Then, navigate to http://locahost (or your local docker ip)

  • To run the Swagger Editor locally using Node:

    git clone https://github.com/swagger-api/swagger-editor.git
    cd swagger-editor
    npm install
    npm start

Fetching dependencies

Use glide.

# in src/notes
$ glide install

Live-Reloading Go Web Applications

Use gin.

go get github.com/codegangsta/gin
$GOPATH/bin/gin

Note: The web application should query the PORT environment variable to know on which port to listen:

// -------  src/notes/main.go  -------

package main

import (
    "net/http"
    "os"
)

func getenv(key string, fallback string) string {
    v := os.Getenv(key)
    if len(v) != 0 {
        return v
    }
    return fallback
}

// used for local testing / debugging
func main() {
    http.ListenAndServe(":" + getenv("PORT", "8080"), nil)
}

Generating swagger documentation

Swagger generation fo Go applications uses go-swagger.
The documentation at https://goswagger.io describes the various go documentation tags that are required for generating swagger documentation.

The following commands should be executed at $GOPATH:

go get -u github.com/go-swagger/go-swagger/cmd/swagger
./bin/swagger generate spec --input=./swagger-aux.json --output=./swagger.json --base-path=./src/notes/app/

Note: The swagger editor does display some warnings for unused attributes and definitions, which I have not been able to eliminate. The good news is that these warnings have no noticeable impact on the resulting rendered swagger documentation page.

Links

About

Example Note Manager in Go (GAE)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages