A quick and easy way to setup a RESTful JSON API
Go-Json-Rest is a thin layer on top of net/http that helps building RESTful JSON APIs easily. It provides fast and scalable request routing using a Trie based implementation, helpers to deal with JSON requests and responses, and middlewares for functionalities like CORS, Auth, Gzip, Status ...
net/http Handler. This standard interface allows combinations with other Handlers.This package is "go-gettable", just do:
go get github.com/ant0ine/go-json-rest/rest
The recommended way of using this library in your project is to use the "vendoring" method, where this library code is copied in your repository at a specific revision. This page is a good summary of package management in Go.
Core Middlewares:
| Name | Description |
|---|---|
| AccessLogApache | Access log inspired by Apache mod_log_config |
| AccessLogJson | Access log with records as JSON |
| AuthBasic | Basic HTTP auth |
| ContentTypeChecker | Verify the request content type |
| Cors | CORS server side implementation |
| Gzip | Compress the responses |
| If | Conditionally execute a Middleware at runtime |
| JsonIndent | Easy to read JSON |
| Jsonp | Response as JSONP |
| PoweredBy | Manage the X-Powered-By response header |
| Recorder | Record the status code and content length in the Env |
| Status | Memecached inspired stats about the requests |
| Timer | Keep track of the elapsed time in the Env |
Third Party Middlewares:
| Name | Description |
|---|---|
| Statsd | Send stats to a statsd server |
| JWT | Provides authentication via Json Web Tokens |
| AuthToken | Provides a Token Auth implementation |
| ForceSSL | Forces SSL on requests |
| SecureRedirect | Redirect clients from HTTP to HTTPS |
If you have a Go-Json-Rest compatible middleware, feel free to submit a PR to add it in this list, and in the examples.
All the following examples can be found in dedicated examples repository: https://github.com/ant0ine/go-json-rest-examples
First examples to try, as an introduction to go-json-rest.
Tradition!
curl demo:
curl -i http://127.0.0.1:8080/
code:
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net/http"
)
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
api.SetApp(rest.AppSimple(func(w rest.ResponseWriter, r *rest.Request) {
w.WriteJson(map[string]string{"Body": "Hello World!"})
}))
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
Demonstrate how to use the relaxed placeholder (notation #paramName).
This placeholder matches everything until the first /, including .
curl demo:
curl -i http://127.0.0.1:8080/lookup/google.com
curl -i http://127.0.0.1:8080/lookup/notadomain
code:
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net"
"net/http"
)
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/lookup/#host", func(w rest.ResponseWriter, req *rest.Request) {
ip, err := net.LookupIP(req.PathParam("host"))
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteJson(&ip)
}),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
Demonstrate simple POST GET and DELETE operations
curl demo:
curl -i -H 'Content-Type: application/json' \
-d '{"Code":"FR","Name":"France"}' http://127.0.0.1:8080/countries
curl -i -H 'Content-Type: application/json' \
-d '{"Code":"US","Name":"United States"}' http://127.0.0.1:8080/countries
curl -i http://127.0.0.1:8080/countries/FR
curl -i http://127.0.0.1:8080/countries/US
curl -i http://127.0.0.1:8080/countries
curl -i -X DELETE http://127.0.0.1:8080/countries/FR
curl -i http://127.0.0.1:8080/countries
curl -i -X DELETE http://127.0.0.1:8080/countries/US
curl -i http://127.0.0.1:8080/countries
code:
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net/http"
"sync"
)
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/countries", GetAllCountries),
rest.Post("/countries", PostCountry),
rest.Get("/countries/:code", GetCountry),
rest.Delete("/countries/:code", DeleteCountry),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
type Country struct {
Code string
Name string
}
var store = map[string]*Country{}
var lock = sync.RWMutex{}
func GetCountry(w rest.ResponseWriter, r *rest.Request) {
code := r.PathParam("code")
lock.RLock()
var country *Country
if store[code] != nil {
country = &Country{}
*country = *store[code]
}
lock.RUnlock()
if country == nil {
rest.NotFound(w, r)
return
}
w.WriteJson(country)
}
func GetAllCountries(w rest.ResponseWriter, r *rest.Request) {
lock.RLock()
countries := make([]Country, len(store))
i := 0
for _, country := range store {
countries[i] = *country
i++
}
lock.RUnlock()
w.WriteJson(&countries)
}
func PostCountry(w rest.ResponseWriter, r *rest.Request) {
country := Country{}
err := r.DecodeJsonPayload(&country)
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if country.Code == "" {
rest.Error(w, "country code required", 400)
return
}
if country.Name == "" {
rest.Error(w, "country name required", 400)
return
}
lock.Lock()
store[country.Code] = &country
lock.Unlock()
w.WriteJson(&country)
}
func DeleteCountry(w rest.ResponseWriter, r *rest.Request) {
code := r.PathParam("code")
lock.Lock()
delete(store, code)
lock.Unlock()
w.WriteHeader(http.StatusOK)
}
Demonstrate how to use Method Values.
Method Values have been introduced in Go 1.1.
This shows how to map a Route to a method of an instantiated object (i.e: receiver of the method)
curl demo:
curl -i -H 'Content-Type: application/json' \
-d '{"Name":"Antoine"}' http://127.0.0.1:8080/users
curl -i http://127.0.0.1:8080/users/0
curl -i -X PUT -H 'Content-Type: application/json' \
-d '{"Name":"Antoine Imbert"}' http://127.0.0.1:8080/users/0
curl -i -X DELETE http://127.0.0.1:8080/users/0
curl -i http://127.0.0.1:8080/users
code:
package main
import (
"fmt"
"github.com/ant0ine/go-json-rest/rest"
"log"
"net/http"
"sync"
)
func main() {
users := Users{
Store: map[string]*User{},
}
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/users", users.GetAllUsers),
rest.Post("/users", users.PostUser),
rest.Get("/users/:id", users.GetUser),
rest.Put("/users/:id", users.PutUser),
rest.Delete("/users/:id", users.DeleteUser),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
type User struct {
Id string
Name string
}
type Users struct {
sync.RWMutex
Store map[string]*User
}
func (u *Users) GetAllUsers(w rest.ResponseWriter, r *rest.Request) {
u.RLock()
users := make([]User, len(u.Store))
i := 0
for _, user := range u.Store {
users[i] = *user
i++
}
u.RUnlock()
w.WriteJson(&users)
}
func (u *Users) GetUser(w rest.ResponseWriter, r *rest.Request) {
id := r.PathParam("id")
u.RLock()
var user *User
if u.Store[id] != nil {
user = &User{}
*user = *u.Store[id]
}
u.RUnlock()
if user == nil {
rest.NotFound(w, r)
return
}
w.WriteJson(user)
}
func (u *Users) PostUser(w rest.ResponseWriter, r *rest.Request) {
user := User{}
err := r.DecodeJsonPayload(&user)
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
return
}
u.Lock()
id := fmt.Sprintf("%d", len(u.Store)) // stupid
user.Id = id
u.Store[id] = &user
u.Unlock()
w.WriteJson(&user)
}
func (u *Users) PutUser(w rest.ResponseWriter, r *rest.Request) {
id := r.PathParam("id")
u.Lock()
if u.Store[id] == nil {
rest.NotFound(w, r)
u.Unlock()
return
}
user := User{}
err := r.DecodeJsonPayload(&user)
if err != nil {
rest.Error(w, err.Error(), http.StatusInternalServerError)
u.Unlock()
return
}
user.Id = id
u.Store[id] = &user
u.Unlock()
w.WriteJson(&user)
}
func (u *Users) DeleteUser(w rest.ResponseWriter, r *rest.Request) {
id := r.PathParam("id")
u.Lock()
delete(u.Store, id)
u.Unlock()
w.WriteHeader(http.StatusOK)
}
Common use cases, found in many applications.
Combine Go-Json-Rest with other handlers.
api.MakeHandler() is a valid http.Handler, and can be combined with other handlers.
In this example the api handler is used under the /api/ prefix, while a FileServer is instantiated under the /static/ prefix.
curl demo:
curl -i http://127.0.0.1:8080/api/message
curl -i http://127.0.0.1:8080/static/main.go
code:
package main
import (
"github.com/ant0ine/go-json-rest/rest"
"log"
"net/http"
)
func main() {
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/message", func(w rest.ResponseWriter, req *rest.Request) {
w.WriteJson(map[string]string{"Body": "Hello World!"})
}),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
http.Handle("/api/", http.StripPrefix("/api", api.MakeHandler()))
http.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("."))))
log.Fatal(http.ListenAndServe(":8080", nil))
}
Demonstrate basic CRUD operation using a store based on MySQL and GORM
GORM is simple ORM library for Go. In this example the same struct is used both as the GORM model and as the JSON model.
curl demo:
curl -i -H 'Content-Type: application/json' \
-d '{"Message":"this is a test"}' http://127.0.0.1:8080/reminders
curl -i http://127.0.0.1:8080/reminders/1
curl -i http://127.0.0.1:8080/reminders
curl -i -X PUT -H 'Content-Type: application/json' \
-d '{"Message":"is updated"}' http://127.0.0.1:8080/reminders/1
curl -i -X DELETE http://127.0.0.1:8080/reminders/1
code: ``` go package main
import ( "github.com/ant0ine/go-json-rest/rest" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" "log" "net/http" "time" )
func main() {
i := Impl{}
i.InitDB()
i.InitSchema()
api := rest.NewApi()
api.Use(rest.DefaultDevStack...)
router, err := rest.MakeRouter(
rest.Get("/reminders", i.GetAllReminders),
rest.Post("/reminders", i.PostReminder),
rest.Get("/reminders/:id", i.GetReminder),
rest.Put("/reminders/:id", i.PutReminder),
rest.Delete("/reminders/:id", i.DeleteReminder),
)
if err != nil {
log.Fatal(err)
}
api.SetApp(router)
log.Fatal(http.ListenAndServe(":8080", api.MakeHandler()))
}
type Reminder struct {
Id int64 json:"id"
Message string sql:"size:1024" json:"message"
CreatedAt time.Time json:"createdAt"
UpdatedAt time.Time json:"updatedAt"
DeletedAt time.Time json:"-"
}
type Impl struct { DB *gorm.DB }
func (i *Impl) InitDB() { var err error i.DB, err = gorm.Open("mysql", "gorm:gorm@/gorm?charset=utf8&parseTime=True") if err != nil { log.Fatalf("Got error when connect database, the error is '%v'", err) } i.DB.LogMode(true) }
func (i *Impl) InitSchema() { i.DB.AutoMigrate(&Reminder{
$ claude mcp add go-json-rest \
-- python -m otcore.mcp_server <graph>