Generating an rest api with go-gen-api
tl;dr
I generated an api with go-gen-api to speed up my development process.
Introduction
I often work with databases and rest services. Mostly I need to connect both services, doing some tasks on the database via the rest api, e.g. creating, updating or deleting records.
I had like 7 Tables and for each Table I needed to create a create
, update
, delete
CRUD operation (Users, Groups, AccessRoles, Tokens, Resources, …). I don’t know how you create these CRUD operations but I was sick of writing the same code always again:
func Create(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" || r.Method != "PUT" {
throw some error
}
if err := json.unmarshal(r.Body, &newUserStruct); err != nil {
throw some error
}
if err := validateUserStruct(newUserStruct); err != nil {
throw some error
}
if err := createNewUserOnTable(&newUserStruct); err != nil {
throw some error
}
send everything worked response
}
Writing a generator
So the objective is clear: Write an generator that auto generates all those crud operations
Thoughts
- It must be easy, no primary keys, no forign keys, no m2n
- Generate go files before the main projects needs them.
- Most of the code is the same so use some kind of template, there is text/template, use that
- Split the API into two parts RESTAPI and API, so we can use the generator for non rest api projects aswell
Coding
The main challenge was the structuring and templating, during coding I noticed that tables have null values, thats why the generated structs always use pointers.
Also how should the update work? The generator does not know what the primary key is (and I dont want him to know, remember? It should be simple). The solution is to split the request in a find
and update
field, meaning that the application will use the find
field to find the records it needs to update and using the update
field to set the values.
Another feature that is required for mostly all requirements is the Hooks feature. It enables you to modify the requested field before it gets passed to the database and at the same time it allows you to set a custom response for the query.
Finalizing
After working a few hours on the generator and I decided it is good enough for my needs, I stitched an example. (You can find it here). I realized that the code is still, well, a lot. At least I saved the whole Marshaling/Unmarshaling and Database operations, so it makes life a bit easier.
So what can we do now?
- We have an API that can be used for Database manipulation
- We have an REST-API that has an
create
,delete
,update
andget
function for each specified table.
Sample of generation
type User struct {
ID int
Name string
Password string
}
type Group struct {
ID int
Name string
}
type GroupMember struct {
ID int
UserID int
GroupID int
}
err := gogenapi.Generate(&gogenapi.Config{
Structs: []interface{}{&User{}, &Group{}, &GroupMember{}},
OutputPath: "gogenapi",
})
if err != nil {
panic(err)
}
Sample of usage
package main
import (
"database/sql"
"log"
"gogenapi/user" // Path to generated user
"github.com/gorilla/mux"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "user.sqlite")
router := mux.NewRouter()
restAPI := user.NewRestAPI(router.PathPrefix("/user").Subrouter(), user.New(db))
err = http.ListenAndServe(":8000", router)
if err != nil {
log.Fatal(err)
}
}