类似 Twitter 的 API 服务

这个示例演示如何使用 MongoDB,JWT 和 JSON 创建一个类似 Twitter 的 REST API 服务。

模型

user.go

  1. package model
  2. import "gopkg.in/mgo.v2/bson"
  3. type (
  4. User struct {
  5. ID bson.ObjectId `json:"id" bson:"_id,omitempty"`
  6. Email string `json:"email" bson:"email"`
  7. Password string `json:"password,omitempty" bson:"password"`
  8. Token string `json:"token,omitempty" bson:"-"`
  9. Followers []string `json:"followers,omitempty" bson:"followers,omitempty"`
  10. }
  11. )

post.go

  1. package model
  2. import "gopkg.in/mgo.v2/bson"
  3. type (
  4. Post struct {
  5. ID bson.ObjectId `json:"id" bson:"_id,omitempty"`
  6. To string `json:"to" bson:"to"`
  7. From string `json:"from" bson:"from"`
  8. Message string `json:"message" bson:"message"`
  9. }
  10. )

控制器

handler.go

  1. package handler
  2. import mgo "gopkg.in/mgo.v2"
  3. type (
  4. Handler struct {
  5. DB *mgo.Session
  6. }
  7. )
  8. const (
  9. // Key (Should come from somewhere else).
  10. Key = "secret"
  11. )

user.go

  1. package handler
  2. import (
  3. "net/http"
  4. "time"
  5. jwt "github.com/dgrijalva/jwt-go"
  6. "github.com/labstack/echo"
  7. "github.com/labstack/echo/cookbook/twitter/model"
  8. mgo "gopkg.in/mgo.v2"
  9. "gopkg.in/mgo.v2/bson"
  10. )
  11. func (h *Handler) Signup(c echo.Context) (err error) {
  12. // Bind
  13. u := &model.User{ID: bson.NewObjectId()}
  14. if err = c.Bind(u); err != nil {
  15. return
  16. }
  17. // Validate
  18. if u.Email == "" || u.Password == "" {
  19. return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid email or password"}
  20. }
  21. // Save user
  22. db := h.DB.Clone()
  23. defer db.Close()
  24. if err = db.DB("twitter").C("users").Insert(u); err != nil {
  25. return
  26. }
  27. return c.JSON(http.StatusCreated, u)
  28. }
  29. func (h *Handler) Login(c echo.Context) (err error) {
  30. // Bind
  31. u := new(model.User)
  32. if err = c.Bind(u); err != nil {
  33. return
  34. }
  35. // Find user
  36. db := h.DB.Clone()
  37. defer db.Close()
  38. if err = db.DB("twitter").C("users").
  39. Find(bson.M{"email": u.Email, "password": u.Password}).One(u); err != nil {
  40. if err == mgo.ErrNotFound {
  41. return &echo.HTTPError{Code: http.StatusUnauthorized, Message: "invalid email or password"}
  42. }
  43. return
  44. }
  45. //-----
  46. // JWT
  47. //-----
  48. // Create token
  49. token := jwt.New(jwt.SigningMethodHS256)
  50. // Set claims
  51. claims := token.Claims.(jwt.MapClaims)
  52. claims["id"] = u.ID
  53. claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
  54. // Generate encoded token and send it as response
  55. u.Token, err = token.SignedString([]byte(Key))
  56. if err != nil {
  57. return err
  58. }
  59. u.Password = "" // Don't send password
  60. return c.JSON(http.StatusOK, u)
  61. }
  62. func (h *Handler) Follow(c echo.Context) (err error) {
  63. userID := userIDFromToken(c)
  64. id := c.Param("id")
  65. // Add a follower to user
  66. db := h.DB.Clone()
  67. defer db.Close()
  68. if err = db.DB("twitter").C("users").
  69. UpdateId(bson.ObjectIdHex(id), bson.M{"$addToSet": bson.M{"followers": userID}}); err != nil {
  70. if err == mgo.ErrNotFound {
  71. return echo.ErrNotFound
  72. }
  73. }
  74. return
  75. }
  76. func userIDFromToken(c echo.Context) string {
  77. user := c.Get("user").(*jwt.Token)
  78. claims := user.Claims.(jwt.MapClaims)
  79. return claims["id"].(string)
  80. }

post.go

  1. package handler
  2. import (
  3. "net/http"
  4. "strconv"
  5. "github.com/labstack/echo"
  6. "github.com/labstack/echo/cookbook/twitter/model"
  7. mgo "gopkg.in/mgo.v2"
  8. "gopkg.in/mgo.v2/bson"
  9. )
  10. func (h *Handler) CreatePost(c echo.Context) (err error) {
  11. u := &model.User{
  12. ID: bson.ObjectIdHex(userIDFromToken(c)),
  13. }
  14. p := &model.Post{
  15. ID: bson.NewObjectId(),
  16. From: u.ID.Hex(),
  17. }
  18. if err = c.Bind(p); err != nil {
  19. return
  20. }
  21. // Validation
  22. if p.To == "" || p.Message == "" {
  23. return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid to or message fields"}
  24. }
  25. // Find user from database
  26. db := h.DB.Clone()
  27. defer db.Close()
  28. if err = db.DB("twitter").C("users").FindId(u.ID).One(u); err != nil {
  29. if err == mgo.ErrNotFound {
  30. return echo.ErrNotFound
  31. }
  32. return
  33. }
  34. // Save post in database
  35. if err = db.DB("twitter").C("posts").Insert(p); err != nil {
  36. return
  37. }
  38. return c.JSON(http.StatusCreated, p)
  39. }
  40. func (h *Handler) FetchPost(c echo.Context) (err error) {
  41. userID := userIDFromToken(c)
  42. page, _ := strconv.Atoi(c.QueryParam("page"))
  43. limit, _ := strconv.Atoi(c.QueryParam("limit"))
  44. // Defaults
  45. if page == 0 {
  46. page = 1
  47. }
  48. if limit == 0 {
  49. limit = 100
  50. }
  51. // Retrieve posts from database
  52. posts := []*model.Post{}
  53. db := h.DB.Clone()
  54. if err = db.DB("twitter").C("posts").
  55. Find(bson.M{"to": userID}).
  56. Skip((page - 1) * limit).
  57. Limit(limit).
  58. All(&posts); err != nil {
  59. return
  60. }
  61. defer db.Close()
  62. return c.JSON(http.StatusOK, posts)
  63. }

API

注册

用户注册

  • 用请求里取出用户信息验证合法性。
  • 不合法的邮箱和密码,返回 400 - Bad Request
  • 合法的邮箱和密码,保存数据到数据库并返回 201 - Created

请求

  1. curl \
  2. -X POST \
  3. http://localhost:1323/signup \
  4. -H "Content-Type: application/json" \
  5. -d '{"email":"jon@labstack.com","password":"shhh!"}'

响应

201 - Created

  1. {
  2. "id": "58465b4ea6fe886d3215c6df",
  3. "email": "jon@labstack.com",
  4. "password": "shhh!"
  5. }

Login

User login

  • Retrieve user credentials from the body and validate against database.
  • For invalid credentials, send 401 - Unauthorized response.
  • For valid credentials, send 200 - OK response:
    • Generate JWT for the user and send it as response.
    • Each subsequent request must include JWT in the Authorization header.

Method: POST

Path: /login

Request

  1. curl \
  2. -X POST \
  3. http://localhost:1323/login \
  4. -H "Content-Type: application/json" \
  5. -d '{"email":"jon@labstack.com","password":"shhh!"}'

Response

200 - OK

  1. {
  2. "id": "58465b4ea6fe886d3215c6df",
  3. "email": "jon@labstack.com",
  4. "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODEyNjUxMjgsImlkIjoiNTg0NjViNGVhNmZlODg2ZDMyMTVjNmRmIn0.1IsGGxko1qMCsKkJDQ1NfmrZ945XVC9uZpcvDnKwpL0"
  5. }

Client should store the token, for browsers, you may use local storage.

Follow

Follow a user

  • For invalid token, send 400 - Bad Request response.
  • For valid token:
    • If user is not found, send 404 - Not Found response.
    • Add a follower to the specified user in the path parameter and send 200 - OK response.

Method: POST

Path: /follow/:id

Request

  1. curl \
  2. -X POST \
  3. http://localhost:1323/follow/58465b4ea6fe886d3215c6df \
  4. -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODEyNjUxMjgsImlkIjoiNTg0NjViNGVhNmZlODg2ZDMyMTVjNmRmIn0.1IsGGxko1qMCsKkJDQ1NfmrZ945XVC9uZpcvDnKwpL0"

Response

200 - OK

Post

Post a message to specified user

  • For invalid request payload, send 400 - Bad Request response.
  • If user is not found, send 404 - Not Found response.
  • Otherwise save post in the database and return it via 201 - Created response.

Method: POST

Path: /posts

Request

  1. curl \
  2. -X POST \
  3. http://localhost:1323/posts \
  4. -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODEyNjUxMjgsImlkIjoiNTg0NjViNGVhNmZlODg2ZDMyMTVjNmRmIn0.1IsGGxko1qMCsKkJDQ1NfmrZ945XVC9uZpcvDnKwpL0" \
  5. -H "Content-Type: application/json" \
  6. -d '{"to":"58465b4ea6fe886d3215c6df","message":"hello"}'

Response

201 - Created

  1. {
  2. "id": "584661b9a6fe8871a3804cba",
  3. "to": "58465b4ea6fe886d3215c6df",
  4. "from": "58465b4ea6fe886d3215c6df",
  5. "message": "hello"
  6. }

Feed

List most recent messages based on optional page and limit query parameters

Method: GET

Path: /feed?page=1&limit=5

Request

  1. curl \
  2. -X GET \
  3. http://localhost:1323/feed \
  4. -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0ODEyNjUxMjgsImlkIjoiNTg0NjViNGVhNmZlODg2ZDMyMTVjNmRmIn0.1IsGGxko1qMCsKkJDQ1NfmrZ945XVC9uZpcvDnKwpL0"

Response

200 - OK

  1. [
  2. {
  3. "id": "584661b9a6fe8871a3804cba",
  4. "to": "58465b4ea6fe886d3215c6df",
  5. "from": "58465b4ea6fe886d3215c6df",
  6. "message": "hello"
  7. }
  8. ]