wantedly / apig Goto Github PK
View Code? Open in Web Editor NEW[MOVED TO NEW REPO] Golang RESTful API Server Generator
Home Page: https://github.com/shimastripe/apig
License: MIT License
[MOVED TO NEW REPO] Golang RESTful API Server Generator
Home Page: https://github.com/shimastripe/apig
License: MIT License
execute apig gen
:
~/.go/src/github.com/munisystem/sample go(v1.6.2) ♦ apig gen
[create /Users/muni/.go/src/github.com/munisystem/sample/docs/user.apib] %!s(MISSING)
[create /Users/muni/.go/src/github.com/munisystem/sample/controllers/user.go] %!s(MISSING)
[create /Users/muni/.go/src/github.com/munisystem/sample/docs/index.apib] %!s(MISSING)
[update /Users/muni/.go/src/github.com/munisystem/sample/router/router.go] %!s(MISSING)
[update /Users/muni/.go/src/github.com/munisystem/sample/db/db.go] %!s(MISSING)
[update /Users/muni/.go/src/github.com/munisystem/sample/README.md] %!s(MISSING)
===> Generated...
not generated controller/root.go
:
~/.go/src/github.com/munisystem/sample go(v1.6.2) ♦ go build
# github.com/munisystem/sample/router
router/router.go:10: undefined: controllers.APIEndpoints
~/.go/src/github.com/munisystem/sample go(v1.6.2) ♦
~/.go/src/github.com/munisystem/sample go(v1.6.2) ♦
~/.go/src/github.com/munisystem/sample go(v1.6.2) ♦ ls controllers/
user.go
Add to run generateRootController() when execute apig gen
.
Hi contributors,
Look like this project has been stopped. Is there any alternative ? Or please someone take control to continue contribute this project.
(priority low)
I pretty doubt about necessity of make deps
make deps
make
make install
flow seems really unusual for me. Is there any major app which uses this flow?
If we create makefile, we should follow make standard flow:
make
make install
apig new apig-sample
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/README.md
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/.gitignore
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/main.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/db/db.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/db/pagination.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/router/router.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/middleware/set_db.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/server/server.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/helper/field.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/version/version.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/version/version_test.go
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/controllers/.gitkeep
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/docs/.gitkeep
create /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample/models/.gitkeep
===> Created /Users/awakia/src/github.com/Naoyoshi Aikawa/apig-sample
git config --global github.user awakia
solves this problem, but this is tooooo confusing.
We should somehow solve this problem by asking directory or accepting argument about directory
The instruction in README is not understandable...
Maybe users cannot create servers like what we expected.
We should explain more where to write initial file and gorm model file
gen command
gen command tells apig to generate files (routes, controllers, documents...) from gorm model files you wrote.
Since Heroku API Design says
It is best to provide version specification in the headers, with other metadata, using the Accept header with a custom content type, e.g.:
Accept: application/vnd.heroku+json; version=3
We decided to use
Accept: application/vnd.{{ .User }}+json; version={{ .Version }}
Style.
Though, heroku only says accept header is good, but not about application/vnd
.
This style makes it difficult to parse version a little bit for api provider.
For api user, considering vnd.XXXX part each time to request version is also bothersome.
Let's consider much simpler solution.
Accept: application/json; version=1.0
http://www.django-rest-framework.org/api-guide/versioning/#acceptheaderversioning
At the site there is the reason why provider should use vnd
Strictly speaking the json media type is not specified as including additional parameters. If you are building a well-specified public API you might consider using a vendor media type. To do so, configure your renderers to use a JSON based renderer with a custom media type:
X-API-Version: 1.0
http://kenn.hatenablog.com/entry/2014/03/06/105249
This actually easier to parse. I don't know cons for this though.
Accept: application/vnd.steveklabnik-v2+json
Currently any models that use gorm.Model rather than specifying ID manually are ignored. Models should be picked up when gorm.Model is used.
type Customer struct {
gorm.Model
FirstName string
LastName string
}
When I run make
I get an error:
go generate
main.go:5: running "go-bindata": exec: "go-bindata": executable file not found in $PATH
Makefile:11: recipe for target 'bin/apig' failed
make: *** [bin/apig] Error 1
I was trying to implement same plugin in go.
When i run apig run command on my windows machine, it shows "Invalid import path : github.com\wantedly\Test "
Please help me to rectify this.
Heroku-api-design says "Keep JSON minified in all responses".
https://geemus.gitbooks.io/http-api-design/content/en/responses/keep-json-minified-in-all-responses.html
So pretty JSON output is easy to read for human.
gin has pretty JSON output method.
c.JSON(200, fieldMap)
c.IndentedJSON(200, fieldMap)
However, gin warned,
WARNING: we recommend to use this only for development propuses since printing pretty JSON is more CPU and bandwidth consuming. Use Context.JSON() instead.
I think it does not cost so much to convert pretty JSON output.
How do you think?
Hello,
I saw that no commits has been pushed since october 2017, is this project abandoned ?
Currently, accessing http://localhost:8080/ just returns 404 page not found
.
This behaviour is difficult for new developer to distinguish whether it works or not.
I think, we should return at least 200 ok
@dtan4 what do you think?
https://geemus.gitbooks.io/http-api-design/content/en/responses/nest-foreign-key-relations.html
Summary Goes Here
Serialize foreign key references with a nested object, e.g.:
{
"name": "service-production",
"owner": {
"id": "5d8201b0..."
},
// ...
}
Instead of e.g.:
{
"name": "service-production",
"owner_id": "5d8201b0...",
// ...
}
This approach makes it possible to inline more information about the related resource without having to change the structure of the response or introduce more top-level response fields, e.g.:
{
"name": "service-production",
"owner": {
"id": "5d8201b0...",
"name": "Alice",
"email": "[email protected]"
},
// ...
}
Then now apig works this.
{
"name": "service-production",
"owner_id": "5d8201b0...",
"owner": null
}
so I think apig should work in this way.
{
"name": "service-production",
"owner": {
"id": "5d8201b0...",
}
}
I want to hear everyone's opinion.
gorm
has gorm:"ForeignKey:Profile"
tag.
But gorm
works json:profile_id
tag as gorm:"ForeignKey:Profile"
without gorm:"ForeignKey:Profile"
.
ProfileID uint `json:"profile_id"`
// => ProfileID uint `json:"profile_id" gorm:"ForeignKey:Profile"`
So I don't know what is better that gorm:"ForeignKey:XXX"
was mandatory.
api-server-generator
command is too long to type, I think.
Command name should be short and easy to remember as possible.
Rename api-server-generator
command to the shorter one.
IMO apig
(abbreviation of API-server-Generator
) seems to be good. This is short enough and easy to remember. Additionally, at this time there seems to be no project named apig
on GitHub. This name has uniqueness too.
Let us accept the json request like below:
curl -X POST https://service.com/apps \
-H "Content-Type: application/json" \
-d '{"name": "demoapp"}'
Currently we can accept
curl -X POST https://service.com/apps \
-f 'name=demoapp'
I think we should accept both style
In gin framework, just use BindJSON
instead of Bind
parses such json request
To understand the specification of genereted APIs, we have to prepare the documents.
Generate documents with programs.
API Blueprint is better format of API documents. This format is easy to read and edit because this is based on Markdown. Also, HTML document and API tests are generated easily by using other tools.
Directory structure is below:
|-- docs
|-- index.apib
|-- user.apib
`-- profile.apib
API Blueprint documents are generated inside docs
directory. index.apib
is the top page of documents and has the links to each API pages.
Currently there is no specification of multiple (modulized) files in API Blueprint. There are some solutions by using document generators.
We are going to user aglio for document generator. Therefore the links to other document should be written as below:
# Gist Fox API
# Group Gist
<!-- include(blueprint/gist.apib) -->
<!-- include(blueprint/gists.apib) -->
To reproduce, follow the readme tutorial until the point where you make models/user.go
:
// models/user.go
package models
import "time"
type User struct {
ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"`
Name string `json:"name"`
Emails []Email `json:"emails"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}
and remove the struct tag from a field:
// models/user.go
package models
import "time"
type User struct {
ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id"`
Name string `json:"name"`
Emails []Email `json:"emails"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time
}
Current command line parser implementation is unfortunately horrible...
For example,
apig new --help
apig new -u wantedly sample-server
cannot be parsed as expected.
It just generate the code to --help
or -u
directory.
There is good tool to write CLI like https://github.com/tcnksm/gcli
Why don't we use it? I personally like mitchellh_cli style, but any style of gcli options are acceptable.
@munisystem Please rewrite the code with @dtan4 's help
Now we use db
package as it is in controller. However, we also define variable db
for database object.
It causes conflict of names.
import (
"net/http"
"github.com/wantedly/api-server-generator/examples/simple/db"
"github.com/wantedly/api-server-generator/examples/simple/models"
"github.com/gin-gonic/gin"
)
func GetUsers(c *gin.Context) {
db := db.DBInstance(c)
Define alias (e.g. dbpkg
) for db
package to avoid name conflicts.
import (
"net/http"
dbpkg "github.com/wantedly/api-server-generator/examples/simple/db"
"github.com/wantedly/api-server-generator/examples/simple/models"
"github.com/gin-gonic/gin"
)
When creating API server, people sometimes break restfulness because of the business logics. It is not easy for human to create precisely restful api. So why not generate automatically by computer? The goal of this project is to create api server generator based on model definition, which is basically same with db schema.
The API Server Generator below:
Add model definitions under models/. For example, models/user.go is like below:
package models
import "time"
type User struct {
ID uint `gorm:"primary_key" json:"id"`
Name string `json:"name"`
AccountName string `json:"account_name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
We want to stick with RESTFULLNESS, our generator output is not editable. In other words, our generator is not like rails generator.
In rails, generator creates boilerplates by the command rails new
or rails generate
. After that programmer edit such boilerplates to make a complete server. Rails experience is great, but there are 2 big problems when creating solid api server.
So we decided that generated codes are not editable. Thanks to this design, regenerating whole contents just works even when adding fields.
When some tweaks are needed, we supply before and after hooks.
To reproduce, generated api server receive http request not including Accept
header.
sample code:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
url := "http://localhost:8080/api/users"
req, _ := http.NewRequest(
"GET",
url,
nil,
)
client := new(http.Client)
resp, _ := client.Do(req)
defer resp.Body.Close()
b, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("Response Body: %v", string(b))
}
api server logs:
2016/08/25 11:39:34 [Recovery] panic recovered:
GET /api/users HTTP/1.1
Host: localhost:3000
Accept-Encoding: gzip
User-Agent: Go-http-client/1.1
runtime error: index out of range
/usr/local/Cellar/go/1.6.2/libexec/src/runtime/panic.go:443 (0x402ce99)
gopanic: reflectcall(nil, unsafe.Pointer(d.fn), deferArgs(d), uint32(d.siz), uint32(d.siz))
/usr/local/Cellar/go/1.6.2/libexec/src/runtime/panic.go:27 (0x402b325)
panicindex: panic(indexError)
/Users/muni/.go/src/github.com/wantedly/apig/_example/version/version.go:12 (0x43cb5e7)
New: header := c.Request.Header["Accept"][0]
/Users/muni/.go/src/github.com/wantedly/apig/_example/controllers/user.go:16 (0x42d6b83)
GetUsers: ver, err := version.New(c)
/Users/muni/.go/src/github.com/gin-gonic/gin/context.go:97 (0x40755da)
(*Context).Next: c.handlers[c.index](c)
/Users/muni/.go/src/github.com/wantedly/apig/_example/middleware/set_db.go:12 (0x41072bb)
SetDBtoContext.func1: c.Next()
/Users/muni/.go/src/github.com/gin-gonic/gin/context.go:97 (0x40755da)
(*Context).Next: c.handlers[c.index](c)
/Users/muni/.go/src/github.com/gin-gonic/gin/recovery.go:45 (0x4087ef1)
RecoveryWithWriter.func1: c.Next()
/Users/muni/.go/src/github.com/gin-gonic/gin/context.go:97 (0x40755da)
(*Context).Next: c.handlers[c.index](c)
/Users/muni/.go/src/github.com/gin-gonic/gin/logger.go:63 (0x408718a)
LoggerWithWriter.func1: c.Next()
/Users/muni/.go/src/github.com/gin-gonic/gin/context.go:97 (0x40755da)
(*Context).Next: c.handlers[c.index](c)
/Users/muni/.go/src/github.com/gin-gonic/gin/gin.go:284 (0x407c432)
(*Engine).handleHTTPRequest: context.Next()
/Users/muni/.go/src/github.com/gin-gonic/gin/gin.go:265 (0x407c067)
(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/Cellar/go/1.6.2/libexec/src/net/http/server.go:2081 (0x41e3d8e)
serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/Cellar/go/1.6.2/libexec/src/net/http/server.go:1472 (0x41e063e)
(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/Cellar/go/1.6.2/libexec/src/runtime/asm_amd64.s:1998 (0x405cf41)
goexit: BYTE $0x90 // NOP
[GIN] 2016/08/25 - 11:39:34 | 500 | 1.4057ms | ::1 | GET /api/users
If http request not including Accept
header, c.Request.Header["Accept"] is empty.
(type: map[string][]string)
ref. https://github.com/wantedly/apig/blob/master/_example/version/version.go#L12
I think we should check map length and return that gist using response body.
What do you think?
in README:
- Generate boilerplate
First, creating by apig new command.
$ apig new -u wantedly -v github.com apig-sample
It was actually executed:
[ShotaKubota] ~/src/github.com/wantedly
(*'-') < apig new -u wantedly -v github.com apig-sample
apig version 0.1.0
not created apig-sample
directory:
[ShotaKubota] ~/src/github.com/wantedly
(*'-') < ls -l
total 0
drwxr-xr-x 24 ShotaKubota staff 816 8 4 20:51 apig
exclude -v github.com
:
[ShotaKubota] ~/src/github.com/wantedly
(*'-') < apig new -u wantedly apig-sample
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/models/.gitkeep
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/docs/.gitkeep
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/version/version.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/version/version_test.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/main.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/README.md
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/helper/field_test.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/controllers/.gitkeep
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/middleware/set_db.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/helper/field.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/db/db.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/.gitignore
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/server/server.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/router/router.go
create /Users/ShotaKubota/src/github.com/wantedly/apig-sample/db/pagination.go
===> Created /Users/ShotaKubota/src/github.com/wantedly/apig-sample
created apig-sample
directory:
[ShotaKubota] ~/src/github.com/wantedly
(*'-') < ls -l
total 0
drwxr-xr-x 24 ShotaKubota staff 816 8 4 20:51 apig
drwxr-xr-x 14 ShotaKubota staff 476 8 4 20:58 apig-sample
Filtering with q[field_name] not working if field has different name in json, for example
type User struct {
ID uint `gorm:"primary_key;AUTO_INCREMENT" json:"id" form:"id"`
Pid uint `json:"uid" form:"uid"`
CreatedAt *time.Time `json:"created_at" form:"created_at"`
UpdatedAt *time.Time `json:"updated_at" form:"updated_at"`
}
GET http://localhost:8080/users?q[uid]=42
gives me
{"error":"pq: column \"uid\" does not exist"}
but GET http://localhost:8080/users?q[pid]=42 is OK
it's not a big deal for me, but maybe you should mention it in docs, that q[field_name] is more likely q[column_name]
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.