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.goas controllermodelfolder contains data models one using local data from memory, another is using MongoDB.toolsfolder 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 encodestructinto json binary. Especially, when writestructback to Http Response, we could do:json.NewEncoder(w).Encode(ResponseMessage{Status: "400", Message: "failed"})to send our customstructback directly, wherewishttp.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
bas
b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`)Declare a variable
fwith typeinterface{}andUnmarshalthe 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)
Unmarshlwill populate theParentsfield, change it fromnilto a slice value exists in JSON binary.This is typical of how
Unmarshalworks with the supported reference types (pointers, slices, and maps).
type Foo struct { Bar *Bar }For example, If there were a
Barfield in the JSON object,Unmarshalwould allocate a new Bar and populate it. If not, Bar would be left as anilpointer.
- Therefore, we could
nilto 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/tourTo 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