All Projects → josephspurrier → Gowebapi

josephspurrier / Gowebapi

Licence: mit
Testable Web API in Go with Swagger

Programming Languages

go
31211 projects - #10 most used programming language
golang
3204 projects

Labels

Projects that are alternatives of or similar to Gowebapi

Coolapk Uwp
酷安的UWP版第三方客户端。。
Stars: ✭ 52 (-5.45%)
Mutual labels:  api
Gopay Php Api
GoPay's PHP SDK for Payments REST API
Stars: ✭ 53 (-3.64%)
Mutual labels:  api
Easyjson
Provides an unified JSON access API, you can adapter any JSON library to Gson, Jackson, FastJson with easyjson。 提供了一个JSON门面库,就像slf4j一样。easyjson本身不做json的操作,完全依赖于底层实现库。可以直接使用Easyjson的API,底层的JSON库随时可切换。也可以使用其中某个json的API,然后通过easyjson适配给其他的json库
Stars: ✭ 54 (-1.82%)
Mutual labels:  api
Rest Layer
REST Layer, Go (golang) REST API framework
Stars: ✭ 1,068 (+1841.82%)
Mutual labels:  api
Cakephp Api
API Plugin for CakePHP
Stars: ✭ 53 (-3.64%)
Mutual labels:  api
Ynab Sdk Ruby
YNAB API Ruby Library
Stars: ✭ 54 (-1.82%)
Mutual labels:  api
Sechub
SecHub - one central and easy way to use different security tools with one API/Client
Stars: ✭ 52 (-5.45%)
Mutual labels:  api
Serie Api Rest Node
Código da série de API REST com NodeJS no Youtube
Stars: ✭ 55 (+0%)
Mutual labels:  api
Rest Api Example
RESTful Blog API in Lumen 5.2
Stars: ✭ 53 (-3.64%)
Mutual labels:  api
Lofocats ui
LofoCats UI is a simple web application consuming the LofoCats API, built with Ruby on Rails 🐱
Stars: ✭ 54 (-1.82%)
Mutual labels:  api
Ebook Building An Api Backend With Microprofile
Building an API Backend with MicroProfile
Stars: ✭ 53 (-3.64%)
Mutual labels:  api
Modio Unity
Unity Plugin for integrating mod.io - a modding API for game developers
Stars: ✭ 53 (-3.64%)
Mutual labels:  api
Simple Salesforce
A very simple Salesforce.com REST API client for Python
Stars: ✭ 1,072 (+1849.09%)
Mutual labels:  api
Trueopenvr Core
Core, docs
Stars: ✭ 52 (-5.45%)
Mutual labels:  api
Pink Trombone
A programmable version of Neil Thapen's Pink Trombone
Stars: ✭ 54 (-1.82%)
Mutual labels:  api
Doorman
Doorman is an authorization (micro)service.
Stars: ✭ 52 (-5.45%)
Mutual labels:  api
Lara Vue Auth
A repo for laravel vue authentication tutorial.
Stars: ✭ 54 (-1.82%)
Mutual labels:  api
Dsc Mercado Livre
Biblioteca de integração com o Mercado Livre
Stars: ✭ 55 (+0%)
Mutual labels:  api
Githubapi
Swift implementation of Github REST API v3
Stars: ✭ 55 (+0%)
Mutual labels:  api
Lumen Api Starter
Quickstarter for Lumen
Stars: ✭ 54 (-1.82%)
Mutual labels:  api

gowebapi

Go Report Card Build Status Coverage Status

Swagger Validator

Testable Web API in Go with Swagger

This project demonstrates how to structure and build an API using the Go language without a framework. Only carefully chosen packages are included. Dredd is used to test the generated Swagger spec against the API to ensure it's correct.

Older Version: The previous version that was around for a while was 0.1-alpha. If you want to see that code, you can view the tag. The current version is a significant refactor that follows better practices.

Note: You cannot use go get with this repository. You should perform a git clone then set your GOPATH to the folder that git clone created called gowebapi. This allows you to easily fork the repository and build your own applications without rewritting any import paths.

You must use Go 1.7 or newer because this project uses the http context.

Quick Start with Docker Compose

You can build a docker image from this repository and set it up along with a MySQL container using docker compose.

# Create a docker image.
docker build -t webapi:latest .

# Launch MySQL and the webapi with docker compose.
docker-compose up

# Open your browser to http://localhost:8080 and you should be able to access the API.
# Try using the Swagger spec:
# http://petstore.swagger.io/?url=https://raw.githubusercontent.com/josephspurrier/gowebapi/master/src/app/webapi/swagger.json

# Shutdown the containers.
docker-compose down

Manual Start

Use the following commands to start a MySQL container with Docker:

# Start MySQL without a password.
docker run -d --name=mysql57 -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.7
# or start MySQL with a password.
docker run -d --name=mysql57 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=somepassword mysql:5.7

# Create the database via docker exec.
docker exec mysql57 sh -c 'exec mysql -uroot -e "CREATE DATABASE IF NOT EXISTS webapi DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;"'
# Or create the database manually.
CREATE DATABASE webapi DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci;

# CD to the CLI tool.
cd src/app/webapi/cmd/cliapp

# Build the CLI tool.
go build

# Apply the database migrations without a password.
DB_USERNAME=root DB_HOSTNAME=127.0.0.1 DB_PORT=3306 DB_DATABASE=webapi ./cliapp migrate all ../../../../../migration/mysql-v0.sql
# or apply the database migrations with a password.
DB_USERNAME=root DB_PASSWORD=somepassword DB_HOSTNAME=127.0.0.1 DB_PORT=3306 DB_DATABASE=webapi ./cliapp migrate all ../../../../../migration/mysql-v0.sql

Using the database connection information above, follow the steps to set up the config.json file:

# Copy the config.json from the root of the project to the CLI app folder.
cp config.json src/app/webapi/cmd/webapi/config.json

# Edit the `Database` section so the connection information matches your MySQL instance.
# The database password is read from the `config.json` file, but is overwritten by the environment variable, `DB_PASSWORD`, if it is set.

# Generate a base64 encoded secret.
./cliapp generate

# Use the encoded secret above to replace the `JWT.Secret` value in the config.

Now you can start the API.

# CD to the webapi app folder.
cd src/app/webapi/cmd/webapi

# Build the app.
go build

# Run the app.
./webapi

# Open your browser to this URL to see the **welcome** message and status **OK**: http://localhost:8080/v1

To interact with the API, open your favorite REST client.

You'll need to authenticate with at http://localhost:8080/v1/auth before you can use any of the user endpoints. Once you have a token, add it to the request header with a name of Authorization and with a value of Bearer {TOKEN HERE}. To create a user, send a POST request to http://localhost:8080/v1/user with the following fields: first_name, last_name, email, and password.

Currently, only a Content-Type of application/x-www-form-urlencoded is supported when sending to the API.

Available Endpoints

The following endpoints are available:

* POST   /v1/user           - Create a new user
* GET	 /v1/user/{user_id} - Retrieve a user by ID
* GET	 /v1/user           - Retrieve a list of all users
* PUT	 /v1/user/{user_id} - Update a user by ID
* DELETE /v1/user/{user_id} - Delete a user by ID
* DELETE /v1/user           - Delete all users

Swagger

This projects uses Swagger v2 to document the API. The entire Swagger spec is generated from the code in this repository.

The Swagger UI linked back to this project can be viewed here.

The Swagger spec JSON file is available here.

Install Swagger

This tool will generate the Swagger spec from annotations in the Go code. It will read the comments in the code and will pull types from structs.

go get github.com/go-swagger/go-swagger/cmd/swagger

Generate Swagger Spec

# CD to the webapi folder.
cd src/app/webapi

# Generate the swagger spec.
swagger generate spec -o ./swagger.json

# Replace 'example' with 'x-example' in the swagger spec.
## MacOS
sed -i '' -e 's/example/x\-example/' ./swagger.json
## Linux
sed -i'' -e 's/example/x\-example/' ./swagger.json

# Validate the swagger spec.
swagger validate ./swagger.json

# Serve the spec for the browser.
swagger serve -F=swagger ./swagger.json

Dredd

This projects uses Dredd to test the Swagger spec against the API. Since the Swagger spec is generated from annotations in the Go code, it's good to ensure there are no discrepancies.

The Go documentation for Dredd is here.

Sample output from Dredd is here.

Install Dredd

You must have MySQL running for these tests to pass.

# Install dredd.
npm install -g dredd

# Get the goodman package for Go hooks.
go get github.com/snikch/goodman/cmd/goodman

# CD to the webapi folder.
cd src/app/webapi

# Copy the testdata/config.json to the current directory.
cp testdata/config.json ./config.json

# Build the hooks app to load the test data.
go build -o ./cmd/hooks/hooks app/webapi/cmd/hooks

# Start MySQL without a password.
docker run -d --name=mysql57 -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.7

# Run a test with dredd.
dredd

Vendoring

This project uses dep. The dep init command was run from inside the src/app/webapi folder.

These packages are used in the project:

Folder Structure

All the Go code is inside the src folder. This allows you to easily fork this project to use and test it. You'll just need to set your GOPATH to the gowebapi folder after you do a git clone (don't do a go get, it will not work).

In the src/app/webapi folder, you see a few top level folders:

  • cmd - contains the main function and a static folder for the favicon.
  • component - contains sets of related endpoints and database code.
  • internal - contains project specific packages with dependencies.
  • middleware - contains http wrappers for logging and CORS.
  • model - contains the files with JSON structs that will outputted by the API.
  • pkg - contains generic packages without project specific dependencies - these can be safely moved to other projects without internal dependencies.
  • store - contains the files with SQL used to query the database.

Components

In the root of the src/app/webapi/component folder, you see:

  • core.go - contains the dependencies shared by all the components: logger, database connection, request bind/validation, and the responses.
  • core_mock.go - contains the mocked dependencies which can be used by tests to modify the mocked dependencies.
  • interface.go - contains all the interfaces for the dependencies so you can easily mock out each one for testing purposes.

Inside each component, you see a component.go file which contains the main struct and all the routes. You'll also see individual files for each endpoint with Swagger annotations and the tests for each endpoint.

Store

In the store folder, you see user.go which has the SQL queries. Notice how IDatabase and the IQuery are passed into each store. This provides a unified way to run database queries and also provides a base set of simple SQL queries so you don't have to rewrite them for every table:

  • FindOneByID(dest query.IRecord, ID string) (found bool, err error)
  • FindAll(dest query.IRecord) (total int, err error)
  • ExistsByID(db query.IRecord, s string) (found bool, err error)
  • ExistsByField(db query.IRecord, field string, value string) (found bool, ID string, err error)
  • DeleteOneByID(dest query.IRecord, ID string) (affected int, err error)
  • DeleteAll(dest query.IRecord) (affected int, err error)

This is not an ORM - it just provides you with a simple query builder. Since the struct has an anonymous field, component.IQuery, you can overwrite any of the functions.

For instance, to retrieve a single user from the database, you would use this code:

// Create the store.
u := store.NewUser(p.DB, p.Q)

// Get a user.
exists, err := u.FindOneByID(u, req.UserID)

The code for the generic FindOneByID() is in the pkg/query/query.go file:

// FindOneByID will find a record by string ID.
func (q *Q) FindOneByID(dest IRecord, ID string) (exists bool, err error) {
	err = q.db.Get(dest, fmt.Sprintf(`
		SELECT * FROM %s
		WHERE %s = ?
		LIMIT 1`, dest.Table(), dest.PrimaryKey()),
		ID)
	return recordExists(err)
}

If you wanted to change the query so it excludes deleted users, you could add a new function to the store/user.go file so it looks like this:

// FindOneByID will find a record by string ID excluding deleted records.
func (x *User) FindOneByID(dest query.IRecord, ID string) (exists bool, err error) {
	err = x.db.Get(dest, fmt.Sprintf(`
		SELECT * FROM %s
		WHERE %s = ?
		AND deleted_at  IS NULL
		LIMIT 1`, dest.Table(), dest.PrimaryKey()),
		ID)

	if err == nil {
		return true, nil
	} else if err == sql.ErrNoRows {
		return false, nil
	}
	return false, err
}

This allows you to standardize on how to interact with your database models throughout the team.

Endpoint HTTP Handlers

In order to make the endpoints error driven, all the http handler functions must return an int and an error. This allows error handling to be centralized in the webapi.go file by setting the router.ServeHTTP variable. You can see the routes in the component/user/component.go file:

// Routes will set up the endpoints.
func (p *Endpoint) Routes(router component.IRouter) {
	router.Post("/v1/user", p.Create)
	router.Get("/v1/user/:user_id", p.Show)
	router.Get("/v1/user", p.Index)
	router.Put("/v1/user/:user_id", p.Update)
	router.Delete("/v1/user/:user_id", p.Destroy)
	router.Delete("/v1/user", p.DestroyAll)
}

The endpoints are separated into files under each component folder and they look like this:

func (p *Endpoint) DestroyAll(w http.ResponseWriter, r *http.Request) (int, error) {
	// Create the store.
	u := store.NewUser(p.DB, p.Q)

	// Delete all items.
	count, err := u.DeleteAll(u)
	if err != nil {
		return http.StatusInternalServerError, err
	} else if count < 1 {
		return http.StatusBadRequest, errors.New("no users to delete")
	}

	return p.Response.OK(w, "users deleted")
}

Request Validation

The app/webapi/internal/bind is a wrapper around the github.com/go-playground/validator package so it can validate structs. You can view the user/create.go file to see where the email validation and the required validation is specified in the tags:

// swagger:parameters UserCreate
type request struct {
	// in: formData
	// Required: true
	FirstName string `json:"first_name" validate:"required"`
	// in: formData
	// Required: true
	LastName string `json:"last_name" validate:"required"`
	// in: formData
	// Required: true
	Email string `json:"email" validate:"required,email"`
	// in: formData
	// Required: true
	Password string `json:"password" validate:"required"`
}

// Request validation.
req := new(request)
if err := p.Bind.FormUnmarshal(req, r); err != nil {
	return http.StatusBadRequest, err
} else if err = p.Bind.Validate(req); err != nil {
	return http.StatusBadRequest, err
}

Reflection

The app/webapi/internal/bind and the app/webapi/pkg/structcopy packages use reflection. The bind package will take the form parameters from the request object and map them to a struct. The structcopy package will copy the values from the SQL store structs and set the fields on the JSON model structs based on the JSON tags.

func (p *Endpoint) Index(w http.ResponseWriter, r *http.Request) (int, error) {
	// Create the DB store.
	u := store.NewUser(p.DB, p.Q)

	// Get all items.
	results := make(store.UserGroup, 0)
	_, err := u.FindAll(&results)
	if err != nil {
		return http.StatusInternalServerError, err
	}

	// Copy the items to the JSON model.
	arr := make([]model.UserIndexResponseData, 0)
	for _, u := range results {
		item := new(model.UserIndexResponseData)
		err = structcopy.ByTag(&u, "db", item, "json")
		if err != nil {
			return http.StatusInternalServerError, err
		}
		arr = append(arr, *item)
	}

	// Send the response.
	resp := new(model.UserIndexResponse)
	resp.Body.Status = http.StatusText(http.StatusOK)
	resp.Body.Data = arr
	return p.Response.JSON(w, resp.Body)
}

Logging

You can disable logging on the server by setting an environment variable: WEBAPI_LOG_LEVEL=none

Testing

All the tests use a database called: webapitest. The quickest way to get it set up is:

# Launch MySQL in docker container.
docker run -d --name=mysql57 -p 3306:3306 -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.7

# Create the database via docker exec.
docker exec mysql57 sh -c 'exec mysql -uroot -e "CREATE DATABASE IF NOT EXISTS webapitest DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;"'

# Or create the database manually.
CREATE DATABASE webapitest DEFAULT CHARSET = utf8 COLLATE = utf8_unicode_ci;

You can use these commands to run tests:

# CD to the folder.
cd src/app/webapi

# Test all the packages.
go test ./...

# Get coverage of all tests.
go test -coverpkg=all ./...

# Get the coverage map of the current folder.
go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html && open cover.html && sleep 5 && rm cover.html && rm cover.out

# Get the coverage map of all the packages.
go test -coverprofile cover.out ./... && go tool cover -html=cover.out -o cover.html && open cover.html && sleep 5 && rm cover.html && rm cover.out

# Get the total code coverage - this only takes into consideration packages that
# have a test file in them.
go test ./... -coverprofile cover.out; go tool cover -func cover.out

Conventions

Rules for mapping HTTP methods to CRUD:

POST   - Create (add record into database)
GET    - Read (get record from the database)
PUT    - Update (edit record in the database)
DELETE - Delete (remove record from the database)

Rules for HTTP status codes:

* Create something            - 201 (Created)
* Read something              - 200 (OK)
* Update something            - 200 (OK)
* Delete something            - 200 (OK)
* Missing request information - 400 (Bad Request)
* Unauthorized operation      - 401 (Unauthorized)
* Any other error             - 500 (Internal Server Error)

Resources

These are all good reads:

Custom HTTP Handlers:

Package Layout:

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].