Build a RESTful API using Golang(updating)
1. Requirement
Build a RESTful API that can create/read(get)/update/delete/ aka CRUD
user data from a persistence database, with the following User model
{ "id": "as user id", "name": "as user name", "dob": "as date of birth", "address": "as user address", "description": "as user description", "createdAt": "as user created time" }
1.1. Functionality
- The API should follow typical RESTful API design pattern.
- The data should be saved in DB (NoSQL DB is preferred).
- Provide proper unit test.
- Provide proper API document.
- (Optional), user auth using OAuth.
- (Optional), a complete logging strategy.
- (Optional), user instances need to link to each other. How to design the model and what API you would build for querying or modifying it?
- (Optional), supppose the address of user now includes a geographic coordinate(latitude and longitude), can you build an API that
- given a user name
- return the nearby friends
- given a user name
2. Golang code structure
zw@DESKTOP-TTOPQ58:manageUser$ tree . . ├── go.mod ├── go.sum ├── main.go ├── model │ ├── index.go │ ├── local.go │ └── mongodb.go └── tools └── common.go 2 directories, 7 files
main.go
as controllermodel
folder contains data models one using local data from memory, another is using MongoDB.tools
folder contains common useful functions.
3. Process JSON
Hanlding JSON places an important role in our service since it is the format we communicate with clients.
3.1. Encoding and Decoding
In general, we use standard package encoding/json
Unmarshal
, which decode the json binary intostruct
.Marshal
, which encodestruct
into json binary. Especially, when writestruct
back to Http Response, we could do:json.NewEncoder(w).Encode(ResponseMessage{Status: "400", Message: "failed"})
to send our customstruct
back directly, wherew
ishttp.ResponseWriter
.
3.2. Decoding arbitrary data
The interface{}
(empty interface) type describes an interface with zero methods. Every Go type implements at least zero methods and therefore satisfies the empty interface. We use it to represent arbitrary data.
Suppose there is a binary data stored in variable
b
as
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)
Declare a variable
f
with typeinterface{}
andUnmarshal
the binary into it.
var f interface{} err := json.Unmarshal(b, &f)
Use type assertion to reveal its underlying structure:
a map whose keys are strings and whose values are themselves stored as empty interface values
.
m := f.(map[string]interface{})
At last, iterate through the map using a type switch to access its values as their concrete types:
for k, v := range m { switch vv := v.(type) { case string: fmt.Println(k, "is string", vv) case float64: fmt.Println(k, "is float64", vv) case []interface{}: fmt.Println(k, "is an array:") for i, u := range vv { fmt.Println(i, u) } default: fmt.Println(k, "is of a type I don't know how to handle") } }
3.3. Refernece Type
For decoding arbitrary data, we convert the binary into a map whose keys are strings and whose values are themselves stored as empty interface values. In fact, we could directly Unmarshal
the binary into an existing struct.
type FamilyMember struct { Name string Age int Parents []string } var m FamilyMember err := json.Unmarshal(b, &m)
Unmarshl
will populate theParents
field, change it fromnil
to a slice value exists in JSON binary.This is typical of how
Unmarshal
works with the supported reference types (pointers, slices, and maps).
type Foo struct { Bar *Bar }
For example, If there were a
Bar
field in the JSON object,Unmarshal
would allocate a new Bar and populate it. If not, Bar would be left as anil
pointer.
- Therefore, we could
nil
to test if some attribute exists in from JSON binary if that attribute is one of pointers, slices, and maps.
4. Persistent data using MongoDB
For simple demo, we will use MongoDB Atlas.
- Follow through the steps from Get started with MongoDB Atlas.
- Click on the cluster you just created –> click tab “Collections” –> click “+ Create Database”
- Use the code example from Connect to MongoDB Atlas to test your first connection with your database.
- Corresponding MongoDB types could be viewed at mongo-driver.
5. Useful Notes
5.1. cURL
Get all Users
curl http://localhost:10000/users -i
Create a single User using POST request with json format
curl -i -X POST -H 'Content-Type: application/json' -d '{"id":"test01", "description":"test"}' http://localhost:10000/user
Get a single User by its id
curl http://localhost:10000/user/test01 -i
Delete a single user by its id using DELETE request
curl -i -X DELETE http://localhost:10000/user/test01
5.2. Golang proxy
When install MongoDB Go driver, it is not accessible from China, so to install it, we need to set Golang proxy
Set proxy according to your location
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct # Or other proxy go env -w GOPROXY=https://goproxy.io/zh/,direct
Test proxy setting works
time go get golang.org/x/tour
To unset proxy
go env -u GOPROXY # Now it should show default one: go env | grep proxy # GOPROXY="https://proxy.golang.org,direct"
6. References
- RESTful
- JSON
- MongoDB
- OAuth and JWT
- Deployment