Skip to content

Instantly share code, notes, and snippets.

@nmerouze nmerouze/main.go
Last active Mar 30, 2019

Embed
What would you like to do?
JSON-API with Go and MongoDB: Final Part
package main
import (
"encoding/json"
"log"
"net/http"
"reflect"
"time"
"github.com/gorilla/context"
"github.com/julienschmidt/httprouter"
"github.com/justinas/alice"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// Repo
type Tea struct {
Id bson.ObjectId `json:"id,omitempty" bson:"_id,omitempty"`
Name string `json:"name"`
Category string `json:"category"`
}
type TeasCollection struct {
Data []Tea `json:"data"`
}
type TeaResource struct {
Data Tea `json:"data"`
}
type TeaRepo struct {
coll *mgo.Collection
}
func (r *TeaRepo) All() (TeasCollection, error) {
result := TeasCollection{[]Tea{}}
err := r.coll.Find(nil).All(&result.Data)
if err != nil {
return result, err
}
return result, nil
}
func (r *TeaRepo) Find(id string) (TeaResource, error) {
result := TeaResource{}
err := r.coll.FindId(bson.ObjectIdHex(id)).One(&result.Data)
if err != nil {
return result, err
}
return result, nil
}
func (r *TeaRepo) Create(tea *Tea) error {
id := bson.NewObjectId()
_, err := r.coll.UpsertId(id, tea)
if err != nil {
return err
}
tea.Id = id
return nil
}
func (r *TeaRepo) Update(tea *Tea) error {
err := r.coll.UpdateId(tea.Id, tea)
if err != nil {
return err
}
return nil
}
func (r *TeaRepo) Delete(id string) error {
err := r.coll.RemoveId(bson.ObjectIdHex(id))
if err != nil {
return err
}
return nil
}
// Errors
type Errors struct {
Errors []*Error `json:"errors"`
}
type Error struct {
Id string `json:"id"`
Status int `json:"status"`
Title string `json:"title"`
Detail string `json:"detail"`
}
func WriteError(w http.ResponseWriter, err *Error) {
w.Header().Set("Content-Type", "application/vnd.api+json")
w.WriteHeader(err.Status)
json.NewEncoder(w).Encode(Errors{[]*Error{err}})
}
var (
ErrBadRequest = &Error{"bad_request", 400, "Bad request", "Request body is not well-formed. It must be JSON."}
ErrNotAcceptable = &Error{"not_acceptable", 406, "Not Acceptable", "Accept header must be set to 'application/vnd.api+json'."}
ErrUnsupportedMediaType = &Error{"unsupported_media_type", 415, "Unsupported Media Type", "Content-Type header must be set to: 'application/vnd.api+json'."}
ErrInternalServer = &Error{"internal_server_error", 500, "Internal Server Error", "Something went wrong."}
)
// Middlewares
func recoverHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %+v", err)
WriteError(w, ErrInternalServer)
}
}()
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func loggingHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
next.ServeHTTP(w, r)
t2 := time.Now()
log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t2.Sub(t1))
}
return http.HandlerFunc(fn)
}
func acceptHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Accept") != "application/vnd.api+json" {
WriteError(w, ErrNotAcceptable)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func contentTypeHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "application/vnd.api+json" {
WriteError(w, ErrUnsupportedMediaType)
return
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func bodyHandler(v interface{}) func(http.Handler) http.Handler {
t := reflect.TypeOf(v)
m := func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
val := reflect.New(t).Interface()
err := json.NewDecoder(r.Body).Decode(val)
if err != nil {
WriteError(w, ErrBadRequest)
return
}
if next != nil {
context.Set(r, "body", val)
next.ServeHTTP(w, r)
}
}
return http.HandlerFunc(fn)
}
return m
}
// Main handlers
type appContext struct {
db *mgo.Database
}
func (c *appContext) teasHandler(w http.ResponseWriter, r *http.Request) {
repo := TeaRepo{c.db.C("teas")}
teas, err := repo.All()
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/vnd.api+json")
json.NewEncoder(w).Encode(teas)
}
func (c *appContext) teaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
repo := TeaRepo{c.db.C("teas")}
tea, err := repo.Find(params.ByName("id"))
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/vnd.api+json")
json.NewEncoder(w).Encode(tea)
}
func (c *appContext) createTeaHandler(w http.ResponseWriter, r *http.Request) {
body := context.Get(r, "body").(*TeaResource)
repo := TeaRepo{c.db.C("teas")}
err := repo.Create(&body.Data)
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/vnd.api+json")
w.WriteHeader(201)
json.NewEncoder(w).Encode(body)
}
func (c *appContext) updateTeaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
body := context.Get(r, "body").(*TeaResource)
body.Data.Id = bson.ObjectIdHex(params.ByName("id"))
repo := TeaRepo{c.db.C("teas")}
err := repo.Update(&body.Data)
if err != nil {
panic(err)
}
w.WriteHeader(204)
w.Write([]byte("\n"))
}
func (c *appContext) deleteTeaHandler(w http.ResponseWriter, r *http.Request) {
params := context.Get(r, "params").(httprouter.Params)
repo := TeaRepo{c.db.C("teas")}
err := repo.Delete(params.ByName("id"))
if err != nil {
panic(err)
}
w.WriteHeader(204)
w.Write([]byte("\n"))
}
// Router
type router struct {
*httprouter.Router
}
func (r *router) Get(path string, handler http.Handler) {
r.GET(path, wrapHandler(handler))
}
func (r *router) Post(path string, handler http.Handler) {
r.POST(path, wrapHandler(handler))
}
func (r *router) Put(path string, handler http.Handler) {
r.PUT(path, wrapHandler(handler))
}
func (r *router) Delete(path string, handler http.Handler) {
r.DELETE(path, wrapHandler(handler))
}
func NewRouter() *router {
return &router{httprouter.New()}
}
func wrapHandler(h http.Handler) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
context.Set(r, "params", ps)
h.ServeHTTP(w, r)
}
}
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
appC := appContext{session.DB("test")}
commonHandlers := alice.New(context.ClearHandler, loggingHandler, recoverHandler, acceptHandler)
router := NewRouter()
router.Get("/teas/:id", commonHandlers.ThenFunc(appC.teaHandler))
router.Put("/teas/:id", commonHandlers.Append(contentTypeHandler, bodyHandler(TeaResource{})).ThenFunc(appC.updateTeaHandler))
router.Delete("/teas/:id", commonHandlers.ThenFunc(appC.deleteTeaHandler))
router.Get("/teas", commonHandlers.ThenFunc(appC.teasHandler))
router.Post("/teas", commonHandlers.Append(contentTypeHandler, bodyHandler(TeaResource{})).ThenFunc(appC.createTeaHandler))
http.ListenAndServe(":8080", router)
}
@DerekStrickland

This comment has been minimized.

Copy link

DerekStrickland commented Dec 5, 2014

Nice! Enjoyed the whole article series.

@bakins

This comment has been minimized.

Copy link

bakins commented Dec 17, 2014

Like the article. Would rather see the use of keyed literals rather than this form&Error{"bad_request", 400, "Bad request", "Request body is not well-formed. It must be JSON."} Some folks just blindly copy paste, so I prefer the "longer" form because of clarity and https://golang.org/doc/go1compat#expectations

@bmatsuo

This comment has been minimized.

Copy link

bmatsuo commented Dec 22, 2014

Nice article. Thanks for writing it.

But I noticed there are a couple errors in your handlers. These two lines for example

https://gist.github.com/nmerouze/2e26a02d23c4c62173fd#file-main-go-L229-L230

Calling w.Header().Set(...) after calling w.WriteHeader(...) does not effect the response on the wire. The godoc for "net/http" is explicit about this.

@nmerouze

This comment has been minimized.

Copy link
Owner Author

nmerouze commented Dec 27, 2014

Thanks for catching the problem @bmatsuo

@st3fan

This comment has been minimized.

Copy link

st3fan commented Jul 11, 2016

Aren't you supposed to Copy the MongoDB session per request?

@ijunaid8989

This comment has been minimized.

Copy link

ijunaid8989 commented Dec 19, 2016

@nmerouze out of interest have you moved on to Elixir?

@robincher

This comment has been minimized.

Copy link

robincher commented Sep 22, 2018

Thanks for sharing!

@yusfianG

This comment has been minimized.

Copy link

yusfianG commented Mar 13, 2019

please help, i have probeleum how to save consume api result to mongo
my code its error !!!

package main

import (
"encoding/json"
"fmt"
"bytes"
"io/ioutil"
"net/http"
//"mydb"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)

type Response struct {
StatusCode int json:"statusCode"
Headers map[string]string json:"headers"
Body string json:"body"
}

type Passanger struct {
bson.ObjectId json:"id" bson:"_id"
Title string json:"title" bson:"title"
Image string json:"image" bson:"image"
Price string json:"price" bson:"price"
Rating string json:"rating" bson:"rating"
}

//type Passanger struct {
//ID bson.ObjectId bson:"_id,omitempty"
//Title string
//Image string
//Price string
//Rating string
//}

var (
IsDrop = true
)

type Adapter func(http.Handler) http.Handler
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
for _, adapter := range adapters {
h = adapter(h)
}
return h
}

func main() {
fmt.Println("Starting the application...")

url := "http://" 
fmt.Println("URL:>", url)


jsonData := map[string]string{}
jsonValue, _ := json.Marshal(jsonData)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue))
req.Header.Set("Content-Type", "application/json")

// req.Header.Set("Authorization", strAuthorization)

client := &http.Client{}

response, err := client.Do(req)

var info map[string]interface{}
var infodata string

if err != nil {
    fmt.Printf("The HTTP request failed with error %s\n", err)
} else {
    data, _ := ioutil.ReadAll(response.Body)
	//defer response.Body.Close()
	//json.Unmarshal(data, &info)
    fmt.Printf("ALTEA Response: %s\n", string(data))	    		
}


fmt.Println("Save Data To Database...")


session, err := mgo.Dial("localhost")
if err != nil {
	panic(err)
}

defer session.Close()

session.SetMode(mgo.Monotonic, true)

// Drop Database
if IsDrop {
	err = session.DB("test").DropDatabase()
	if err != nil {
		panic(err)
	}
}

// Collection Passanger
c := session.DB("flylist").C("passanger")

// Index
index := mgo.Index{
	Key:        []string{"title", "image", "price", "rating"},
	Unique:     true,
	DropDups:   true,
	Background: true,
	Sparse:     true,
}

err = c.EnsureIndex(index)
if err != nil {
	panic(err)
}


// Insert Datas
infodata = info["data"].(map[string]interface{})["id"].(string)
//konversi:= []byte(info)
err = c.Insert(&Passanger{Title: string(infodata[0]), Image: string(infodata[1]), Price: string(infodata[2]), Rating: string(infodata[3])})
if err != nil {
	panic(err)

}

// Query All
var results []Passanger
err = c.Find(bson.M{"name": "Ale"}).Sort("-timestamp").All(&results)

if err != nil {
	panic(err)
}
fmt.Println("Results All: ", results)

}

Please help Me ,!

@yusfianG

This comment has been minimized.

Copy link

yusfianG commented Mar 13, 2019

Please help , i have problem when try to save consume api result to mongodb, the code it's error

package main

import (
"encoding/json"
"fmt"
"bytes"
"io/ioutil"
"net/http"
//"mydb"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)

type Response struct {
StatusCode int json:"statusCode"
Headers map[string]string json:"headers"
Body string json:"body"
}

type Passanger struct {
bson.ObjectId json:"id" bson:"_id"
Title string json:"title" bson:"title"
Image string json:"image" bson:"image"
Price string json:"price" bson:"price"
Rating string json:"rating" bson:"rating"
}

//type Passanger struct {
//ID bson.ObjectId bson:"_id,omitempty"
//Title string
//Image string
//Price string
//Rating string
//}

var (
IsDrop = true
)

type Adapter func(http.Handler) http.Handler
func Adapt(h http.Handler, adapters ...Adapter) http.Handler {
for _, adapter := range adapters {
h = adapter(h)
}
return h
}

func main() {
fmt.Println("Starting the application...")

url := "http://" 
fmt.Println("URL:>", url)


jsonData := map[string]string{}
jsonValue, _ := json.Marshal(jsonData)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonValue))
req.Header.Set("Content-Type", "application/json")

// req.Header.Set("Authorization", strAuthorization)

client := &http.Client{}

response, err := client.Do(req)

var info map[string]interface{}
var infodata string

if err != nil {
    fmt.Printf("The HTTP request failed with error %s\n", err)
} else {
    data, _ := ioutil.ReadAll(response.Body)
	//defer response.Body.Close()
	//json.Unmarshal(data, &info)
    fmt.Printf("ALTEA Response: %s\n", string(data))	    		
}


fmt.Println("Save Data To Database...")


session, err := mgo.Dial("localhost")
if err != nil {
	panic(err)
}

defer session.Close()

session.SetMode(mgo.Monotonic, true)

// Drop Database
if IsDrop {
	err = session.DB("test").DropDatabase()
	if err != nil {
		panic(err)
	}
}

// Collection Passanger
c := session.DB("flylist").C("passanger")

// Index
index := mgo.Index{
	Key:        []string{"title", "image", "price", "rating"},
	Unique:     true,
	DropDups:   true,
	Background: true,
	Sparse:     true,
}

err = c.EnsureIndex(index)
if err != nil {
	panic(err)
}


// Insert Datas
infodata = info["data"].(map[string]interface{})["id"].(string)
//konversi:= []byte(info)
err = c.Insert(&Passanger{Title: string(infodata[0]), Image: string(infodata[1]), Price: string(infodata[2]), Rating: string(infodata[3])})
if err != nil {
	panic(err)

}

// Query All
var results []Passanger
err = c.Find(bson.M{"name": "Ale"}).Sort("-timestamp").All(&results)

if err != nil {
	panic(err)
}
fmt.Println("Results All: ", results)

}

Please correct my code, thanks alot before

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.