├── .gitignore ├── README.md ├── config └── config.example.json ├── glide.lock ├── glide.yaml ├── helpers ├── database │ └── mongodb.go └── wrapper │ └── wrapper.go ├── main.go ├── middleware └── middleware.go └── src └── modules └── user ├── handler └── handler.go ├── model └── user.go └── repository ├── repository.go └── repository_mongo.go /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /config/config.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-echo-mongo 2 | 3 | Simple Golang REST application with Echo Framework & MongoDB 4 | 5 | ## How to run 6 | 7 | These are the steps to run this app: 8 | 9 | 1. Make sure Golang,Glide(Go Package Manager), and MongoDB are installed 10 | 2. Clone this repository to your dir, eg. ```$GOPATH/src/github.com/``` 11 | 3. Go to project root directory (```$GOPATH/src/github.com//go-echo-mongo```) 12 | 4. Populate the config file ```./config/config.example.json``` with your own configuration and copy to ```./config/config.json``` 13 | 5. Run command ```glide install``` to install the dependencies 14 | 6. Start the app with command ```go run main.go``` 15 | 16 | ## Application 17 | 18 | >The request header should contain: 19 | ```Content-Type: "application/json"``` 20 | >The error response should be: 21 | 22 | ```json 23 | { 24 | "code": "", 25 | "data": null, 26 | "message":"Error message", 27 | "success": false 28 | } 29 | ``` 30 | 31 | >The success response should be: 32 | 33 | ```json 34 | { 35 | "code": "", 36 | "data": "", 37 | "message":"Success message", 38 | "success": true 39 | } 40 | ``` 41 | 42 | These are the list of endpoint: 43 | 44 | Method | URI | Description 45 | ------------ | ---------------- | ------------- 46 | POST | /users | Create new user. 47 | GET | /users/<:userID> | Get user by ID. 48 | GET | /users | Get list of user. 49 | 50 | ## References 51 | 52 | - [YouTube] () 53 | - [GitHub] () 54 | -------------------------------------------------------------------------------- /config/config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "name": "go-echo-mongo", 4 | "domain": "localhost", 5 | "port": "9000", 6 | "dev": true 7 | }, 8 | "database": { 9 | "mongodb": { 10 | "host":"localhost:27017", 11 | "user": "", 12 | "password": "", 13 | "db": "" 14 | }, 15 | "mysql": { 16 | "host":"localhost:3306", 17 | "user": "", 18 | "password": "", 19 | "database": "" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 5ebcc6ab98c519318765fe3d1dd5f619719c7268c72615ecc502307241b2b071 2 | updated: 2019-04-22T00:57:04.409344041+07:00 3 | imports: 4 | - name: github.com/fsnotify/fsnotify 5 | version: 1485a34d5d5723fea214f5710708e19a831720e4 6 | - name: github.com/go-playground/locales 7 | version: f63010822830b6fe52288ee52d5a1151088ce039 8 | subpackages: 9 | - currency 10 | - name: github.com/go-playground/universal-translator 11 | version: 71201497bace774495daed26a3874fd339e0b538 12 | - name: github.com/go-redis/redis 13 | version: d22fde8721cc915a55aeb6b00944a76a92bfeb6e 14 | - name: github.com/hashicorp/hcl 15 | version: 65a6292f0157eff210d03ed1bf6c59b190b8b906 16 | subpackages: 17 | - hcl/ast 18 | - hcl/parser 19 | - hcl/printer 20 | - hcl/scanner 21 | - hcl/strconv 22 | - hcl/token 23 | - json/parser 24 | - json/scanner 25 | - json/token 26 | - name: github.com/labstack/echo 27 | version: 6d9e043284aea2d07f5fcaf0d3a424eb7d9f6109 28 | - name: github.com/labstack/gommon 29 | version: 82ef680aef5189b68682876cf70d09daa4ac0f51 30 | subpackages: 31 | - color 32 | - log 33 | - name: github.com/leodido/go-urn 34 | version: a67a23e1c1af3c66528573bb86a87246477277c1 35 | - name: github.com/magiconair/properties 36 | version: 7757cc9fdb852f7579b24170bcacda2c7471bb6a 37 | - name: github.com/mattn/go-colorable 38 | version: 3a70a971f94a22f2fa562ffcc7a0eb45f5daf045 39 | - name: github.com/mattn/go-isatty 40 | version: c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7 41 | - name: github.com/mitchellh/mapstructure 42 | version: 3536a929edddb9a5b34bd6861dc4a9647cb459fe 43 | - name: github.com/pelletier/go-toml 44 | version: 65b27e6823c427415d156c92d7034dd57a154cf8 45 | - name: github.com/spf13/afero 46 | version: 588a75ec4f32903aa5e39a2619ba6a4631e28424 47 | subpackages: 48 | - mem 49 | - name: github.com/spf13/cast 50 | version: 8c9545af88b134710ab1cd196795e7f2388358d7 51 | - name: github.com/spf13/jwalterweatherman 52 | version: 94f6ae3ed3bceceafa716478c5fbf8d29ca601a1 53 | - name: github.com/spf13/pflag 54 | version: 24fa6976df40757dce6aea913e7b81ade90530e1 55 | - name: github.com/spf13/viper 56 | version: 9e56dacc08fbbf8c9ee2dbc717553c758ce42bc9 57 | - name: github.com/valyala/bytebufferpool 58 | version: cdfbe9377474227bb42120c1e22fd4433e7f69bf 59 | - name: github.com/valyala/fasttemplate 60 | version: 8b5e4e491ab636663841c42ea3c5a9adebabaf36 61 | - name: golang.org/x/crypto 62 | version: df01cb2cc480549d72034218dd98bf97671450ac 63 | subpackages: 64 | - acme 65 | - acme/autocert 66 | - name: golang.org/x/sys 67 | version: e8e3143a4f4a00f1fafef0dd82ba78223281b01b 68 | subpackages: 69 | - unix 70 | - name: golang.org/x/text 71 | version: f4905fbd45b6790792202848439271c74074bbfd 72 | subpackages: 73 | - transform 74 | - unicode/norm 75 | - name: gopkg.in/go-playground/validator.v9 76 | version: b199fa0642d29caca62b6a99d65cc981ba5edc3b 77 | - name: gopkg.in/mgo.v2 78 | version: 9856a29383ce1c59f308dd1cf0363a79b5bef6b5 79 | subpackages: 80 | - bson 81 | - internal/json 82 | - internal/sasl 83 | - internal/scram 84 | - name: gopkg.in/yaml.v2 85 | version: 51d6538a90f86fe93ac480b35f37b2be17fef232 86 | testImports: [] 87 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/sangianpatrick/go-echo-mongo 2 | import: 3 | - package: gopkg.in/mgo.v2 4 | - package: github.com/labstack/echo 5 | version: v4.0.0 6 | - package: gopkg.in/go-playground/validator.v9 7 | version: v9.27.0 8 | - package: github.com/spf13/viper 9 | version: v1.3.2 10 | - package: github.com/go-redis/redis 11 | version: v6.15.2 12 | -------------------------------------------------------------------------------- /helpers/database/mongodb.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "time" 5 | 6 | mgo "gopkg.in/mgo.v2" 7 | ) 8 | 9 | // GetMongoDB is a function that will return MongoDB instance 10 | func GetMongoDB(c map[string]string) (*mgo.Database, error) { 11 | dialInfo, err := mgo.ParseURL(c["host"]) 12 | if err != nil { 13 | return nil, err 14 | } 15 | dialInfo.Timeout = 5 * time.Second 16 | dialInfo.Username = c["user"] 17 | dialInfo.Password = c["password"] 18 | dialInfo.Database = c["db"] 19 | dialInfo.Mechanism = "" 20 | 21 | session, err := mgo.DialWithInfo(dialInfo) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | db := session.DB(c["db"]) 27 | return db, nil 28 | } 29 | -------------------------------------------------------------------------------- /helpers/wrapper/wrapper.go: -------------------------------------------------------------------------------- 1 | package wrapper 2 | 3 | import ( 4 | "github.com/labstack/echo" 5 | ) 6 | 7 | // Props contains properties to be responded 8 | type Props struct { 9 | Code int `json:"code"` 10 | Data interface{} `json:"data"` 11 | Message string `json:"message"` 12 | Success bool `json:"success"` 13 | } 14 | 15 | // Data returns wrapped success data 16 | func Data(code int, data interface{}, message string, c echo.Context) error { 17 | props := &Props{ 18 | Code: code, 19 | Data: data, 20 | Message: message, 21 | Success: true, 22 | } 23 | return c.JSON(code, props) 24 | } 25 | 26 | func Error(code int, message string, c echo.Context) error { 27 | props := &Props{ 28 | Code: code, 29 | Data: nil, 30 | Message: message, 31 | Success: false, 32 | } 33 | return c.JSON(code, props) 34 | } 35 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/labstack/echo" 9 | "github.com/spf13/viper" 10 | 11 | mdl "github.com/sangianpatrick/go-echo-mongo/middleware" 12 | "github.com/sangianpatrick/go-echo-mongo/src/modules/user/handler" 13 | 14 | db "github.com/sangianpatrick/go-echo-mongo/helpers/database" 15 | "github.com/sangianpatrick/go-echo-mongo/src/modules/user/repository" 16 | ) 17 | 18 | func init() { 19 | viper.SetConfigFile(`./config/config.json`) 20 | err := viper.ReadInConfig() 21 | if err != nil { 22 | panic(err) 23 | } 24 | } 25 | 26 | func main() { 27 | var mongoCredential = map[string]string{ 28 | "host": viper.GetString(`database.mongodb.host`), 29 | "user": viper.GetString(`database.mongodb.user`), 30 | "password": viper.GetString(`database.mongodb.password`), 31 | "db": viper.GetString(`database.mongodb.db`), 32 | } 33 | appName := viper.GetString(`app.name`) 34 | appHost := viper.GetString(`app.domain`) 35 | appPort := viper.GetString(`app.port`) 36 | 37 | mongodb, err := db.GetMongoDB(mongoCredential) 38 | if err != nil { 39 | log.Fatal(err) 40 | os.Exit(1) 41 | } 42 | defer mongodb.Logout() 43 | 44 | e := echo.New() 45 | appMiddleware := mdl.InitAppMiddleware(appName) 46 | e.Use(appMiddleware.CORS) 47 | 48 | urMongo := repository.NewUserRepositoryMongo(mongodb, "user") 49 | 50 | handler.NewUserHandler(e, urMongo) 51 | 52 | e.Start(fmt.Sprintf(`%s:%s`, appHost, appPort)) 53 | 54 | } 55 | -------------------------------------------------------------------------------- /middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/labstack/echo" 8 | "github.com/sangianpatrick/go-echo-mongo/helpers/wrapper" 9 | ) 10 | 11 | // AppMiddleware is package that contains function for filtering request 12 | type AppMiddleware struct { 13 | appName string 14 | } 15 | 16 | // CORS is a function that will filter the incoming request 17 | func (am *AppMiddleware) CORS(next echo.HandlerFunc) echo.HandlerFunc { 18 | return func(c echo.Context) error { 19 | contentType := c.Request().Header.Get("Content-Type") 20 | c.Response().Header().Set("Server", am.appName) 21 | c.Response().Header().Set("Access-Control-Allow-Origin", "*") 22 | c.Response().Header().Set("Access-Control-Allow-Methods", 23 | "GET,PUT,POST,DELETE") 24 | c.Response().Header().Set("Access-Control-Allow-Headers", 25 | "Origin, X-Requested-With, Content-Type, Accept") 26 | 27 | c.Response().Header().Set("Accept", "application/json") 28 | if contentType != "application/json" { 29 | fmt.Println(contentType) 30 | return wrapper.Error(http.StatusNotAcceptable, "request is not acceptable due to policy", c) 31 | } 32 | if c.Request().Method == "OPTIONS" { 33 | return c.String(http.StatusOK, "") 34 | } 35 | return next(c) 36 | } 37 | } 38 | 39 | // InitAppMiddleware is a function that act as AppMiddleware constructor 40 | func InitAppMiddleware(appName string) *AppMiddleware { 41 | return &AppMiddleware{ 42 | appName: appName, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/modules/user/handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/labstack/echo" 9 | "github.com/sangianpatrick/go-echo-mongo/helpers/wrapper" 10 | "github.com/sangianpatrick/go-echo-mongo/src/modules/user/model" 11 | "github.com/sangianpatrick/go-echo-mongo/src/modules/user/repository" 12 | validator "gopkg.in/go-playground/validator.v9" 13 | ) 14 | 15 | // UserHandler represent the httphandler for article 16 | type UserHandler struct { 17 | uUcase repository.UserRepository 18 | } 19 | 20 | // NewUserHandler is constructor 21 | func NewUserHandler(e *echo.Echo, ur repository.UserRepository) { 22 | uh := &UserHandler{ 23 | uUcase: ur, 24 | } 25 | e.POST("/users", uh.CreateUser) 26 | e.GET("/users/:userID", uh.GetUser) 27 | e.GET("/users", uh.GetAllUser) 28 | 29 | } 30 | 31 | // GetUser function to get message 32 | func (h *UserHandler) GetUser(c echo.Context) error { 33 | userID := c.Param("userID") 34 | user, err := h.uUcase.FindByID(userID) 35 | if err != nil { 36 | fmt.Println(err) 37 | return wrapper.Error(http.StatusNotFound, err.Error(), c) 38 | } 39 | return wrapper.Data(http.StatusOK, user, "user detail", c) 40 | } 41 | 42 | // GetAllUser is a function to return list of user 43 | func (h *UserHandler) GetAllUser(c echo.Context) error { 44 | users, err := h.uUcase.FindAll() 45 | if err != nil { 46 | return wrapper.Error(http.StatusNotFound, err.Error(), c) 47 | } 48 | if len(users) < 1 { 49 | return wrapper.Data(http.StatusNoContent, nil, "no content", c) 50 | } 51 | return wrapper.Data(http.StatusOK, users, "list of user", c) 52 | } 53 | 54 | // isRequestValid is function that act as request body validator 55 | func isRequestValid(m *model.User) (bool, error) { 56 | 57 | validate := validator.New() 58 | 59 | err := validate.Struct(m) 60 | if err != nil { 61 | return false, err 62 | } 63 | return true, nil 64 | } 65 | 66 | // CreateUser is a function to store new user 67 | func (h *UserHandler) CreateUser(c echo.Context) error { 68 | var user model.User 69 | user.CreatedAt = time.Now() 70 | user.UpdatedAt = time.Now() 71 | 72 | err := c.Bind(&user) 73 | if err != nil { 74 | return wrapper.Error(http.StatusUnprocessableEntity, err.Error(), c) 75 | } 76 | if ok, err := isRequestValid(&user); !ok { 77 | return wrapper.Error(http.StatusBadRequest, err.Error(), c) 78 | } 79 | 80 | err = h.uUcase.Save(&user) 81 | if err != nil { 82 | fmt.Println(err.Error()) 83 | return wrapper.Error(http.StatusConflict, "user is already created", c) 84 | } 85 | return wrapper.Data(http.StatusCreated, nil, "user is created", c) 86 | } 87 | -------------------------------------------------------------------------------- /src/modules/user/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // User is property info 8 | type User struct { 9 | UserID string `bson:"userId" json:"userId" validate:"required"` 10 | FirstName string `bson:"firstName" json:"firstName" validate:"required"` 11 | LastName string `bson:"lastName" json:"lastName" validate:"required"` 12 | Email string `bson:"email" json:"email" validate:"required,email"` 13 | Password string `bson:"password" json:"password" validate:"required"` 14 | CreatedAt time.Time `bson:"createdAt" json:"createdAt"` 15 | UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` 16 | } 17 | 18 | //UserPublic is property that used for a response 19 | type UserPublic struct { 20 | UserID string `bson:"userId" json:"userId"` 21 | FirstName string `bson:"firstName" json:"firstName"` 22 | LastName string `bson:"lastName" json:"lastName"` 23 | Email string `bson:"email" json:"email"` 24 | } 25 | 26 | // Users is a slice of "User" 27 | type Users []UserPublic 28 | -------------------------------------------------------------------------------- /src/modules/user/repository/repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "github.com/sangianpatrick/go-echo-mongo/src/modules/user/model" 5 | ) 6 | 7 | // UserRepository interface is a list of method of user's model 8 | type UserRepository interface { 9 | Save(*model.User) error 10 | Update(string, *model.User) error 11 | Delete(string) error 12 | FindByID(string) (*model.UserPublic, error) 13 | FindAll() (model.Users, error) 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/user/repository/repository_mongo.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "time" 5 | 6 | mgo "gopkg.in/mgo.v2" 7 | "gopkg.in/mgo.v2/bson" 8 | 9 | "github.com/sangianpatrick/go-echo-mongo/src/modules/user/model" 10 | ) 11 | 12 | // userRepositoryMongo is an instance connection of MongoDB 13 | type userRepositoryMongo struct { 14 | db *mgo.Database 15 | collection string 16 | } 17 | 18 | // NewUserRepositoryMongo is function that will return userRepositoryMongo 19 | func NewUserRepositoryMongo(db *mgo.Database, collection string) *userRepositoryMongo { 20 | return &userRepositoryMongo{ 21 | db: db, 22 | collection: collection, 23 | } 24 | } 25 | 26 | // Save is a function to create new user 27 | func (r *userRepositoryMongo) Save(user *model.User) error { 28 | err := r.db.C(r.collection).Insert(user) 29 | return err 30 | } 31 | 32 | // Update is a function to update existing user 33 | func (r *userRepositoryMongo) Update(userID string, user *model.User) error { 34 | user.UpdatedAt = time.Now() 35 | err := r.db.C(r.collection).Update(bson.M{"userId": userID}, user) 36 | return err 37 | } 38 | 39 | // Delete is a function to remove a record from User list 40 | func (r *userRepositoryMongo) Delete(userID string) error { 41 | err := r.db.C(r.collection).Remove(bson.M{"userId": userID}) 42 | return err 43 | } 44 | 45 | // FindByID is a function to get one user by ID 46 | func (r *userRepositoryMongo) FindByID(userID string) (*model.UserPublic, error) { 47 | var user model.UserPublic 48 | err := r.db.C(r.collection).Find(bson.M{"userId": userID}).One(&user) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return &user, nil 53 | } 54 | 55 | // FindAll is a function to get User list 56 | func (r *userRepositoryMongo) FindAll() (model.Users, error) { 57 | var users model.Users 58 | err := r.db.C(r.collection).Find(bson.M{}).All(&users) 59 | if err != nil { 60 | return nil, err 61 | } 62 | return users, nil 63 | } 64 | --------------------------------------------------------------------------------