go run main.go
- Build the search model at http handler
- Build dynamic SQL for search
- Build SQL for paging by page index (page) and page size (limit)
- Build SQL to count total of records
In the below sample, search users with these criteria:
- get users of page "1", with page size "20"
- email="tony": get users with email starting with "tony"
- dateOfBirth between "min" and "max" (between 1953-11-16 and 1976-11-16)
- sort by phone ascending, id descending
{
"page": 1,
"limit": 20,
"sort": "phone,-id",
"email": "tony",
"dateOfBirth": {
"min": "1953-11-16T00:00:00+07:00",
"max": "1976-11-16T00:00:00+07:00"
}
}
GET /users/search?page=1&limit=2&email=tony&dateOfBirth.min=1953-11-16T00:00:00+07:00&dateOfBirth.max=1976-11-16T00:00:00+07:00&sort=phone,-id
In this sample, search users with these criteria:
- get users of page "1", with page size "20"
- email="tony": get users with email starting with "tony"
- dateOfBirth between "min" and "max" (between 1953-11-16 and 1976-11-16)
- sort by phone ascending, id descending
- total: total of users, which is used to calculate numbers of pages at client
- list: list of users
{
"list": [
{
"id": "ironman",
"username": "tony.stark",
"email": "tony.stark@gmail.com",
"phone": "0987654321",
"dateOfBirth": "1963-03-24T17:00:00Z"
}
],
"total": 1
}
- GET: retrieve a representation of the resource
- POST: create a new resource
- PUT: update the resource
- PATCH: perform a partial update of a resource, refer to service and mongo
- DELETE: delete a resource
To check if the service is available.
{
"status": "UP",
"details": {
"mongo": {
"status": "UP"
}
}
}
[
{
"id": "spiderman",
"username": "peter.parker",
"email": "peter.parker@gmail.com",
"phone": "0987654321",
"dateOfBirth": "1962-08-25T16:59:59.999Z"
},
{
"id": "wolverine",
"username": "james.howlett",
"email": "james.howlett@gmail.com",
"phone": "0987654321",
"dateOfBirth": "1974-11-16T16:59:59.999Z"
}
]
GET /users/wolverine
{
"id": "wolverine",
"username": "james.howlett",
"email": "james.howlett@gmail.com",
"phone": "0987654321",
"dateOfBirth": "1974-11-16T16:59:59.999Z"
}
{
"id": "wolverine",
"username": "james.howlett",
"email": "james.howlett@gmail.com",
"phone": "0987654321",
"dateOfBirth": "1974-11-16T16:59:59.999Z"
}
1
PUT /users/wolverine
{
"username": "james.howlett",
"email": "james.howlett@gmail.com",
"phone": "0987654321",
"dateOfBirth": "1974-11-16T16:59:59.999Z"
}
1
Perform a partial update of user. For example, if you want to update 2 fields: email and phone, you can send the request body of below.
PATCH /users/wolverine
{
"email": "james.howlett@gmail.com",
"phone": "0987654321"
}
1
If we pass a struct as a parameter, we cannot control what fields we need to update. So, we must pass a map as a parameter.
type UserService interface {
Update(ctx context.Context, user *User) (int64, error)
Patch(ctx context.Context, user map[string]interface{}) (int64, error)
}
We must solve 2 problems:
- At http handler layer, we must convert the user struct to map, with json format, and make sure the nested data types are passed correctly.
- At repository layer, from json format, we must convert the json format to database format (in this case, we must convert to bson of Mongo)
At http handler layer, we use core-go/service, to convert the user struct to map, to make sure we just update the fields we need to update
import server "github.com/core-go/service"
func (h *UserHandler) Patch(w http.ResponseWriter, r *http.Request) {
var user User
userType := reflect.TypeOf(user)
_, jsonMap := sv.BuildMapField(userType)
body, _ := sv.BuildMapAndStruct(r, &user)
json, er1 := sv.BodyToJson(r, user, body, ids, jsonMap, nil)
result, er2 := h.service.Patch(r.Context(), json)
if er2 != nil {
http.Error(w, er2.Error(), http.StatusInternalServerError)
return
}
respond(w, result)
}
DELETE /users/wolverine
1
- core-go/health: include HealthHandler, HealthChecker, SqlHealthChecker
- core-go/config: to load the config file, and merge with other environments (SIT, UAT, ENV)
- core-go/log: log and log middleware
To check if the service is available, refer to core-go/health
{
"status": "UP",
"details": {
"sql": {
"status": "UP"
}
}
}
To create health checker, and health handler
db, err := sql.Open(conf.Driver, conf.DataSourceName)
if err != nil {
return nil, err
}
sqlChecker := s.NewSqlHealthChecker(db)
healthHandler := health.NewHealthHandler(sqlChecker)
To handler routing
r := mux.NewRouter()
r.HandleFunc("/health", healthHandler.Check).Methods("GET")
To load the config from "config.yml", in "configs" folder
package main
import "github.com/core-go/config"
type Root struct {
DB DatabaseConfig `mapstructure:"db"`
}
type DatabaseConfig struct {
Driver string `mapstructure:"driver"`
DataSourceName string `mapstructure:"data_source_name"`
}
func main() {
var conf Root
err := config.Load(&conf, "configs/config")
if err != nil {
panic(err)
}
}
import (
"github.com/core-go/config"
"github.com/core-go/log"
m "github.com/core-go/middleware"
"github.com/gorilla/mux"
)
func main() {
var conf app.Root
config.Load(&conf, "configs/config")
r := mux.NewRouter()
log.Initialize(conf.Log)
r.Use(m.BuildContext)
logger := m.NewLogger()
r.Use(m.Logger(conf.MiddleWare, log.InfoFields, logger))
r.Use(m.Recover(log.ErrorMsg))
}
To configure to ignore the health check, use "skips":
middleware:
skips: /health