Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/benc-uk/go-rest-api
Boilerplate & template starter for creating a REST based HTTP microservice in Go
https://github.com/benc-uk/go-rest-api
containers go golang microservices rest-api
Last synced: about 2 months ago
JSON representation
Boilerplate & template starter for creating a REST based HTTP microservice in Go
- Host: GitHub
- URL: https://github.com/benc-uk/go-rest-api
- Owner: benc-uk
- License: mit
- Created: 2019-07-02T10:12:41.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2023-11-27T23:49:42.000Z (about 1 year ago)
- Last Synced: 2023-11-28T00:32:37.406Z (about 1 year ago)
- Topics: containers, go, golang, microservices, rest-api
- Language: Go
- Homepage:
- Size: 23.5 MB
- Stars: 10
- Watchers: 3
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: readme.md
- License: LICENSE
Awesome Lists containing this project
README
# Go - REST API Starter Kit & Library
This is a set of packages for creating REST & HTTP based microservices / backend servers in Go, with supporting functions and helpers. It's fairly opinionated and acts a little like a very minimal mini framework.
It purposefully doesn't use any web framework instead focusing on the base HTTP library and [Chi](https://github.com/go-chi/chi) for routing. Approaches such as composition which are idiomatic to Go, rather than classic dependency injection have been used.
The `cmd` folder has an example of a working server/service which will accept REST requests and has a minimal API, serving as a reference.
The `pkg` folder has a number of Go packages to support running REST APIs in Go, these are described in detail below
```
pkg/
├── api
├── auth
├── dapr/pubsub
├── env
├── httptester
├── problem
├── sse
└── static
```This has been developed in Go 1.19+ and follows the https://github.com/golang-standards/project-layout guidelines for project structure. Which might be an acquired taste.
Also included are a standard and reusable Dockerfile & makefile, both of which inject version information into the build. The Makefile will also handle linting and running with hot-reload via `air`
## Package `api`
This is a baseline from which you can extend, in order to run your own API, see the `cmd/server.go` for an example of how this is done. A quick summary is:
```go
import "github.com/benc-uk/go-rest-api/pkg/api"type MyAPI struct {
// Embed and wrap the base API struct
*api.Base
// Add extra fields as per your implementation
foo Foo
}router := chi.NewRouter()
api := MyAPI{
api.NewBase(serviceName, version, buildInfo, healthy),
}api.AddHealthEndpoint(router, "health")
api.AddStatusEndpoint(router, "status")
```The base API supports health checks and exposes data such as version and service name, plus helper functions for sending JSON & plain text responses or errors.
Optional endpoints which can be added to the API:
- Status endpoint, returning server & service details as JSON
- Prometheus metrics
- Health check
- Any routes you wish to return "200 OK" such as the root (/)Optional middleware can be configured:
- Enabling permissive CORS policy (suggest you use chi/cors for finer grained control)
- Enriching HTTP request context with data extracted from JWT token. If a JWT token is found on any request, you can specify a claim, and the value of that claim will be extracted and put into the HTTP request context.Supporting functions of the base API struct are, providing common API use cases:
```go
StartServer(port int, router chi.Router, timeout time.Duration)
ReturnJSON(w http.ResponseWriter, data interface{})
ReturnText(w http.ResponseWriter, msg string)
ReturnErrorJSON(w http.ResponseWriter, err error)
ReturnOKJSON(w http.ResponseWriter)
```## Package `auth`
This package contains `Validator` interface which can be configured and used to enforce authentication on some or all routes of the API.
📝 Note: This package is generic and can be used with any code utilizing the `net/http` library
There are two implementations of the `Validator` interface:
- `PassthroughValidator` - Used when mocking & testing, or to conditionally switch auth off
- `JWTValidator` - Main JWT based validatorThe `JWTValidator` takes three parameters when created:
- *Client ID*: An application client ID used when validating tokens, by checking the `aud` claim.
- *Scope*: A scope string, validated against the `scp` claim.
- *JWKS URL*: A URL of the keystore used to fetch public keys and and verify the signature of the token. This assumes tokens are signed with a public/private key algorithm e.g. RSAIt can be used two ways: `router.Use(jwtValidator.Middleware)` to add validating middleware to all routes on a router. Alternatively `jwtValidator.Protect(myHandler)` to wrap and protect certain handlers
Failed validation results in a HTTP 401 being returned.
## Package `env`
Very basic set of helpers for fetching env vars with fallbacks to default values.
## Package `problem`
Provides support for RFC-7807 standard formatted responses to API errors. Use the `Wrap()` function to wrap an error, and then `Send()` to write it to the HTTP response writer.
```go
// A rather contrived example
func (api MyAPI) getThing(resp http.ResponseWriter, req *http.Request) {
id := "some_id"
thing, err := api.dbContext.ExecuteSomeQuery(id, blah, blah)// Return a RFC-7807 problem wrapping the database error, HTTP 500 will be sent
if err != nil {
problem.Wrap(500, req.RequestURI, "thing:"+id, err).Send(resp)
return
}// Return a RFC-7807 problem describing the missing thing, HTTP 404 will be sent
if thing == nil {
problem.Wrap(404, req.RequestURI, "thing:"+id, errors.New("thing with that ID does not exist")).Send(resp)
return
}// Handle success
}
```## Package `static`
Includes a `SpaHandler` for serving SPA style static applications which may contain client routing logic. It acts as a wrapper around the standard `http.FileServer` but rather than returning 404s it will return a fallback index file, e.g. *index.html*
Usage:
```go
r := chi.NewRouter()r.Handle("/*", static.SpaHandler{
StaticPath: "./", // Path to app content directory
IndexFile: "index.html", // Name of your SPA HTML file
})srv := &http.Server{
Handler: r,
Addr: ":8080",
}log.Fatal(srv.ListenAndServe())
```## Package `httptester`
Used to run through multiple test cases when integration testing an API or any HTTP service. Use the `httptester.TestCase` struct and pass an array of them to `httptester.Run()`
## Package `dapr/pubsub`
Use to register your API with Dapr pub-sub and subscribe to a given topic and register a callback handler for messages received at that topic.
## Package `logging`
Provides `FilteredRequestLogger` an extension of chi middleware logger which supports filtering out of requests from the logging output.
## Package `sse`
Provides support for [Server Side Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). Two implementations are provided:
- Simple backend helper that can stream SSE events over HTTP
- A message broker which can be used to send messages to multiple clients, groups and keep track of connections/disconnectionsNote. This package is standalone and will work with any Go HTTP implementation, you don't need to be using the `api` or the other packages here.
Stream usage:
```go
srv := sse.NewStreamer[string]()// Send the time to the user every 1 second
go func() {
for {
timeNow := time.Now().Format("15:04:05")
srv.Messages <- "Hello it is now " + timeNow
time.Sleep(1 * time.Second)
}
}()http.HandleFunc("/stream-time", func(w http.ResponseWriter, r *http.Request) {
srv.Stream(w, *r)
})
```Broker usage:
```go
srv := sse.NewBroker[string]()// MessageAdapter is optional, but can format messages
srv.MessageAdapter = func(message string, clientID string) sse.SSE {
return sse.SSE{
Event: "message",
Data: "Some prefix " + message,
}
}// Send a message every 1 second
go func() {
for {
srv.SendToAll("Hello! " + time.Now().String())
time.Sleep(1 * time.Second)
}
}()http.HandleFunc("/stream-events", func(w http.ResponseWriter, r *http.Request) {
clientID := "You need to implement something here"
srv.Stream(clientID, w, *r)
})
```