æè¿ãGolang (+echo
) 㧠REST API ãµã¼ããéçºããæ©ä¼ããã£ãã®ã§ããããã¹ããæ¸ããã API ããã¥ã¡ã³ããèªåçæãããããªä»çµã¿ãä½ãããã«è©¦è¡é¯èª¤ããã®ã§ã¡ã¢ã§ãã
æ¹é
- API ããã¥ã¡ã³ãã®çæã«ã¯test2docãå©ç¨
- ãã¹ããå®è¡ãã㨠API Blueprint å½¢å¼ã§ãã¡ã¤ã«ãèªåçæãã¦ãããã
- 該å½ããã¡ã½ããã®ä¸ã«ã³ã¡ã³ããæ¸ããã¨ã§æä½éã®èª¬æã¯è¨è¿°ã§ããã
- README ã«ã¯
gorilla/mux
ã¨julienschmidt/httprouter
ã®ãµã³ãã«ããè¼ã£ã¦ãããecho
ã§ãã¾ãåããã¯è©¦ãã¦ã¿ããããªããã
- ãã¹ãããçæããã
.apib
ãã¡ã¤ã«ãaglioã¿ãããªãã¼ã«ã«ãã¾ãã° HTML ãã¡ã¤ã«ã¨ã㦠API ããã¥ã¡ã³ããã§ããã
ããã¸ã§ã¯ãæ§æ
github.com/danimal141/rest-api-sample
ã¨ããååã§å®è£
ãã¦ãããã¨ããããã¦ã¼ã¶ã¼ä¸è¦§ãè¿ããããªã¨ã³ããã¤ã³ã /api/v1/users
ãå®è£
ãã¦ãAPI ããã¥ã¡ã³ããèªåçæããæ¹æ³ãèããã
ä½è«ã ããGolang ã®ããã±ã¼ã¸ä¾å管çã«depã使ã£ã¦ã¿ãã®ã§ãããé¢é£ã®ãã¡ã¤ã«ãæ··ãã£ã¦ããã
. âââ Gopkg.lock âââ Gopkg.toml âââ api â âââ all.apib â âââ router â â  âââ router.go â âââ v1 â â  âââ users.go â â  âââ users_test.go â â  âââ init_test.go âââ doc âââ gulpfile.js âââ main.go âââ models â âââ users.go âââ node_modules âââ package.json âââ vendor
API ãµã¼ãå®è£
ã¾ã㯠API ãµã¼ãããã£ã¨å®è£ ãã¦ã¿ãã
models/users.go
package models import "fmt" type User struct { Id int UserName string } func SampleUsers() []User { users := make([]User, 0, 10) for i := 0; i < 10; i++ { users = append(users, User{Id: i, UserName: fmt.Sprint("testuser", i)}) } return users }
ã¦ã¼ã¶ã¼ã® Struct ãå®ç¾©ããµã³ãã«å®è£ ãªã®ã§ DB ã«ä¿åçã¯ãã¦ããªãã
api/v1/users.go
package v1 import ( "errors" "fmt" "net/http" "strconv" "github.com/danimal141/rest-api-sample/models" "github.com/labstack/echo" ) type paginationParams struct { Pagination string `query:"pagination"` } /* ## Query parameter key |value |description ----------:|------:|---------------------------- pagination |false |ãã¼ã¸ãã¼ã·ã§ã³æ©è½ã¯æªå®è£ ãªã®ã§falseãå¿ é */ func UsersIndex(c echo.Context) error { if err := validatePaginationParams(c); err != nil { return err } return c.JSON(http.StatusOK, models.SampleUsers()) } func UsersShow(c echo.Context) error { users := models.SampleUsers() id, err := strconv.Atoi(c.Param("user_id")) if err != nil { return err } if id > len(users)-1 { err := fmt.Errorf("user_id=%d is not found", id) return echo.NewHTTPError(http.StatusNotFound, err.Error()) } return c.JSON(http.StatusOK, users[id]) } func validatePaginationParams(c echo.Context) error { p := new(paginationParams) if err := c.Bind(p); err != nil { return err } if p.Pagination != "false" { err := errors.New("pagination must be false, because pagination is not supported yet") return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } return nil }
ã¦ã¼ã¶ã¼ä¸è¦§æ
å ±ãè¿ãUserIndex
ã¨ã¦ã¼ã¶ã¼è©³ç´°æ
å ±ãè¿ãUsersShow
ãå®ç¾©ã
å¾ã§ã©ã®ããã« API ããã¥ã¡ã³ãåæ ããããã確èªãããããä¸è¦§ã¯ãã¼ã¸ãã¼ã·ã§ã³ãæªå®è£
ã§ãããã¨ã確èªãã?pagination=false
ãå¿
é ã§ããã¨ãããã¡ã½ããã®ä¸ã«ã³ã¡ã³ããã¤ãã¦ããã®ãå¾ã§ããã¥ã¡ã³ãã«åæ ããããã§ããã
api/router/router.go
package router import ( "github.com/danimal141/rest-api-sample/api/v1" "github.com/labstack/echo" ) func NewRouter() *echo.Echo { e := echo.New() e1 := e.Group("/api/v1") e1.GET("/users", v1.UsersIndex) e1.GET("/users/:user_id", v1.UsersShow) return e }
ã«ã¼ãã£ã³ã°ã®å®ç¾©ã
main.go
package main import "github.com/danimal141/rest-api-sample/api/router" func main() { r := router.NewRouter() r.Logger.Fatal(r.Start(":8080")) }
ãã㧠go run main.go
ãã¦localhost:8080/api/v1/users/1
ãªã©ã確èªãã㨠JSON ãè¿å´ãããã¯ãã§ããã
ã§ã¯æ¬¡ã«ãã® API ã®ãã¹ããæ¸ã㦠API Blueprint ãã¡ã¤ã«ãèªåçæããä»çµã¿ãä½ã£ã¦ã¿ãã
ãã¹ã
api/v1/init_test.go
package v1_test import ( "log" "net/http" "os" "testing" "github.com/adams-sarah/test2doc/test" "github.com/danimal141/rest-api-sample/api/router" "github.com/labstack/echo" ) var server *test.Server func TestMain(m *testing.M) { var err error r := router.NewRouter() test.RegisterURLVarExtractor(makeURLVarExtractor(r)) server, err = test.NewServer(r) if err != nil { log.Fatal(err.Error()) } // Start test code := m.Run() // Flush to an apib doc file server.Finish() // Terminate os.Exit(code) } func makeURLVarExtractor(e *echo.Echo) func(req *http.Request) map[string]string { return func(req *http.Request) map[string]string { ctx := e.AcquireContext() defer e.ReleaseContext(ctx) pnames := ctx.ParamNames() if len(pnames) == 0 { return nil } paramsMap := make(map[string]string, len(pnames)) for _, name := range pnames { paramsMap[name] = ctx.Param(name) } return paramsMap } }
ãã¡ãã¯ããã¥ã¡ã³ãçæã«å¿ è¦ãªè¨å®çãè¨è¿°ãã¦ããã
ããã§éè¦ãªã®ãvar server *test.Server
ã§ãserver.Finish()
ãå¼ã¶ãã¨ã§ãã¹ãæã®ãªã¯ã¨ã¹ããã¬ã¹ãã³ã¹ãå
ã«.apib
ãã¡ã¤ã«ãçæãã¦ãããã
ã¾ã test.RegisterURLVarExtractor(makeURLVarExtractor(r))
ã¯ãªã¯ã¨ã¹ãã® URL ã«å«ã¾ãããã©ã¡ã¼ã¿é¢é£ã®æ
å ±ãæãã¦ãããããã®ãã®ã§ããããå¼ãã§ãããªãã¨ãã¹ãå®è¡æã« Panic ããã
å
·ä½çã«ã¯ /api/v1/users/1
ã¨ãããªã¯ã¨ã¹ãã§/api/v1/users/:user_id
ã®ãã¹ããããå ´åãmakeURLVarExtractor
ã®è¿ãå¤ã¯map[user_id:1]
ã«ãªããããã¦/api/v1/users/{user_id}
ã¨ããã¨ã³ããã¤ã³ãã®user_id
ã® Example ã¯1
ã®ãããªæ
å ±ãããã¥ã¡ã³ãã«åæ ãããã
api/v1/users_test.go
package v1_test import ( "net/http" "testing" ) func TestUsersIndex(t *testing.T) { url := server.URL + "/api/v1/users?pagination=false" res, err := http.Get(url) if err != nil { t.Errorf("Expected nil, got %v", err) } if res.StatusCode != http.StatusOK { t.Errorf("Expected status code is %d, got %d", http.StatusOK, res.StatusCode) } } func TestUsersShow(t *testing.T) { url := server.URL + "/api/v1/users/1" res, err := http.Get(url) if err != nil { t.Errorf("Expected nil, got %v", err) } if res.StatusCode != http.StatusOK { t.Errorf("Expected status code is %d, got %d", http.StatusOK, res.StatusCode) } }
ä»åã¯ã¨ã©ã¼ã±ã¼ã¹ã¯çç¥ãã¦ããããä¾ãã° /api/v1/users?pagination=true
ãªã©ã¨ãã¦ãã¹ãããã°ããã¥ã¡ã³ãã«BadRequest
ãªæãã§åæ ãããã
ããã¾ã§ã§ go test ./api/v1
ãå®è¡ããã¨api/v1.apib
ãä½æãããããã«ãªãã
api/all.apib
FORMAT: 1A <!-- include(./v1/v1.apib) -->
ä¸å¿all.apib
ãç¨æãã¦ãå°æ¥çã«./v2/v2.apib
ãªã©ã追å ã§ãããããªæ§æãæèãã¦ã¿ãã
API ããã¥ã¡ã³ãçæ
gulp
ã¨aglio
ãå°å
¥ãã¦ããã¹ãã§apib
ãæ´æ°ãããã®ã Watch ã㦠HTML ãä½æããããã«ããã
gulpfile.js
const gulp = require('gulp') const aglio = require('aglio') const gaglio = require('gulp-aglio') const rename = require('gulp-rename') const fs = require('fs') const includePath = process.cwd() + '/api' const paths = aglio.collectPathsSync(fs.readFileSync('api/all.apib', {encoding: 'utf8'}), includePath) gulp.task('build', () => gulp.src('api/all.apib') .pipe(gaglio({template: 'default'})) .pipe(rename('out.html')) .pipe(gulp.dest('doc')) ) gulp.task('watch', () => gulp.watch(paths, ['build']) ) gulp.task('default', ['build', 'watch'])
ãã¨ã¯gulp
ãç«ã¡ä¸ãã¤ã¤ããµã¼ãã®ãã¹ããå®è¡ããã°doc/out.html
ãæ´æ°ãããããã«ãªãã
ã¡ãªã¿ã« ãããªæãã®ããã¥ã¡ã³ããçæãããã
ã¾ã¨ã
ã»ã¼test2doc
ã«å©ããããæã¯ããã¾ããããã¹ãã«ãã API ããã¥ã¡ã³ãã®èªåçæãå®ç¾ã§ãã¾ããã
ãµã³ãã«ã³ã¼ããä¸å¿ãã¡ãã«ããã¦ããã¾ãã®ã§ãä½ãããã®ãå½¹ã«ç«ã¦ãã°å¹¸ãã§ãã