snug

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2022 License: MIT Imports: 8 Imported by: 0

README

Snug Minimal HTTP JSON Api Framework

snug: fitting closely and comfortably: "a snug coat" - Merriam-Webster

Snug is a simple router with the goal of having the bare minimum to put together a JSON web API.

Writing small web apis with Go is barely a chore, it's a pleasure. Repeating the same few simple steps over and over again, I decided to put together this package mostly for quick prototyping. And just to build a router from scratch and do something with reflect.

Provided features:

  • Router with minimal functionalities mimicking http.ServeMux
  • Some default error responses
  • Request body binding with snug.Fit
  • Logging
  • snug.JSON for dumping simple json responses to responsewriter

Example with all the bells and whistles in place:

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/samharju/snug"
)

type server struct {
	router *snug.Router
	db     map[string]int
}

func main() {
	mux := snug.New()

	// init a server with router and a db
	srv := server{
		router: mux,
		db: map[string]int{
			"esa": 25,
		},
	}

	// add logging
	mux.UseMiddleware(snug.Recover)
	mux.UseMiddleware(snug.Logging)

	// register handlers
	mux.Get("/greet", srv.greetanon)
	mux.Post("/greet", srv.postgreet)
	mux.Get("/greet/age/<name>", srv.getAge)

	log.Fatalln(http.ListenAndServe(":8000", mux))
}

// handle url parameter
func (s server) getAge(w http.ResponseWriter, r *http.Request) {
	name := snug.Param(r, "name")
	age, ok := s.db[name]

	if !ok {
		snug.JSON{"error": name + " not found"}.Write(w, 404)
		return
	}

	snug.JSON{"age": age}.Write(w, 200)

}

// endpoint with no input
func (server) greetanon(w http.ResponseWriter, r *http.Request) {
	snug.JSON{"msg": "hello anon"}.Write(w, 200)
}

// reading post request body
func (server) postgreet(w http.ResponseWriter, r *http.Request) {

	var req struct {
		Name *string `json:"name" snug:"required"`
		Age  *int    `json:"age" snug:"required"`
	}

	err := snug.Fit(r.Body, &req)
	if err != nil {
		// return 400 if missing required fields
		snug.JSON{"error": err.Error()}.Write(w, 400)
		return
	}

	snug.JSON{
		"msg": fmt.Sprintf("hello %s %d years", *req.Name, *req.Age),
	}.Write(w, 200)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddJsonHeader

func AddJsonHeader(rw http.ResponseWriter)

Add Content-Type: application/json-header to response.

func Fit

func Fit(data io.ReadCloser, o any) error

Decode response body to given struct pointer.

Error returned is either for invalid json or missing a value for field marked as required. Using snug-tag requires to use also json-tag.

Due to restrictions in encoding/json, use a pointer type for fields that can hold a falsy value. For example empty string or number of value 0 is not interpreted as missing. Using pointers will provide a nil value to return an error if field is tagged as required.

func postgreet(w http.ResponseWriter, r *http.Request) {
	var body struct {
		Name *string `json:"name" snug:"required"`
		Age  *int    `json:"age" snug:"required"`
	}
	err := snug.Fit(r.Body, &body)
	if err != nil {
		// use error for details
		return
	}
	w.Write(200)
}

func Logging

func Logging(next http.HandlerFunc) http.HandlerFunc

Log request info in a generic webserver way to stderr:

timestamp           | remote address             | method |status| response size | url path

example:

2022/09/28 19:21:45 | 161.251.242.12:33706       | GET    | 200 |     56 | /items
2022/09/28 19:21:45 | 161.251.242.12:33706       | GET    | 405 |      0 | /favicon.ico

func Param

func Param(r *http.Request, key string) string

Pull url parameter with name.

r.HandleFunc("GET", "/api/<good>/path/<best>/", func(w http.ResponseWriter, r *http.Request) {
	// Request path /api/pizza/path/beer
	val1 := snug.Param(r, "good") // pizza
	val2 := snug.Param(r, "best") // beer
})

func Recover

func Recover(next http.HandlerFunc) http.HandlerFunc

Middleware for catching panics. When handler panics, server logs the error and responds with status code 500 and body:

{"error": "internal server error"}

Types

type JSON

type JSON map[string]any

JSON type enables easy dumping of contents to http.ResponseWriter.

func (JSON) Write

func (s JSON) Write(w http.ResponseWriter, status int)

Write map as a json payload to responsewriter.

Adds content-type header and given status code. If serialization fails, returns a status 500 and a message:

{"error": "internal server error"}

Dump some fields to JSON and write to http.ResponseWriter:

func hello(w http.ResponseWriter, r *http.Request) {
	m := snug.JSON{
		"msg":   "hello",
		"some":  "field",
		"other": 123,
	}
	m.Write(w, 200)
}

type Middleware

type Middleware func(http.HandlerFunc) http.HandlerFunc

Middleware accepts a http.HandlerFunc and returns a http.HandlerFunc. Perform middleware things before and after calling wrapped handler.

func SomeMiddleware(next http.HandlerFunc) http.HandlerFunc {
	// do things here when handler is registered to a route
	return func(rw http.ResponseWriter, r *http.Request) {
		// do things here before calling next
		next(w, r)
		// do things here after next returns
	}
}

type Router

type Router struct {
	Prefix string

	// NotFound is called when no route matches url.
	// Default handler returns status 404 and a response body:
	// {"error": "not found"}
	NotFound http.HandlerFunc
	// MethodNotAllowed is called when a pattern matches but method for that pattern does not match.
	// Default handler returns status 405 with no body.
	MethodNotAllowed http.HandlerFunc
	// contains filtered or unexported fields
}

func New

func New() *Router

Initialize new snug router.

func (*Router) Delete

func (r *Router) Delete(path string, hf http.HandlerFunc)

Register handler to path with DELETE.

func (*Router) Get

func (r *Router) Get(path string, hf http.HandlerFunc)

Register handler to path with GET.

func (*Router) Handle

func (r *Router) Handle(method, path string, h http.Handler)

Register http.Handler to given method and path.

func (*Router) HandleFunc

func (r *Router) HandleFunc(method, path string, f http.HandlerFunc)

Register http.HandleFunc to given method and path.

func (*Router) Post

func (r *Router) Post(path string, hf http.HandlerFunc)

Register handler to path with POST.

func (*Router) Put

func (r *Router) Put(path string, hf http.HandlerFunc)

Register handler to path with PUT.

func (Router) ServeHTTP

func (ro Router) ServeHTTP(rw http.ResponseWriter, r *http.Request)

ServeHTTP routes request to appropriate handler.

func (*Router) UseMiddleware

func (r *Router) UseMiddleware(mw Middleware)

Slap given middleware to all handlers to be registered on this router.

	r := snug.New()
	mw := func(hf http.HandlerFunc) http.HandlerFunc {
		return func(rw http.ResponseWriter, r *http.Request) {
			fmt.Println("middleware called")
			hf(rw, r)
		}
	}
	r.UseMiddleware(mw)

	r.HandleFunc("GET", "/path", func(rw http.ResponseWriter, r *http.Request) {
		fmt.Println("handler called")
	})

 // GET /path
 // middleware called
 // handler called

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL