├── .env.example ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── app ├── config │ └── Database.go ├── ctrl │ ├── ApiCtrl.go │ └── UserCtrl.go ├── lib │ ├── BaseModel.go │ ├── Middleware.go │ ├── Request.go │ └── Response.go ├── middlewares │ ├── Auth0Middleware.go │ ├── CORSMiddleware.go │ ├── JWTMiddleware.go │ ├── LogMiddleware.go │ ├── SecureMiddleware.go │ └── say-something.go ├── models │ └── User.go ├── router.go └── server.go ├── icon.png └── main.go /.env.example: -------------------------------------------------------------------------------- 1 | ENVIRONMENT="development" 2 | 3 | # 4 | # API Information 5 | API_TITLE="Boilerplate-API" 6 | API_VERSION="0.0.0" 7 | API_PORT=3000 8 | 9 | # 10 | # Database configurations 11 | DB_DIALECT="mysql" 12 | DB_CONNECTION="user:password@tcp(localhost:3306)/database_name?charset=utf8&parseTime=True" 13 | 14 | # 15 | # Logger 16 | LOG_LEVEL="debug" 17 | 18 | # 19 | # AUTH0 configurations 20 | AUTH0_BASE_URL="http://localhost:3333" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS generated files # 2 | .DS_Store 3 | Thumbs.db 4 | 5 | # IDEA generated files # 6 | .idea/ 7 | *.swp 8 | .awcache 9 | 10 | # Go generated files # 11 | tmp/ 12 | .env 13 | debug 14 | 15 | # Logs # 16 | *.log 17 | *.log* 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Package", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "program": "${workspaceRoot}" 10 | } 11 | // { 12 | // "name": "Launch", 13 | // "type": "go", 14 | // "request": "launch", 15 | // "mode": "debug", 16 | // "remotePath": "", 17 | // "port": 2345, 18 | // "host": "127.0.0.1", 19 | // "program": "${fileDirname}", 20 | // // "program": "main.go", 21 | // "env": {}, 22 | // "args": [], 23 | // "showLog": true 24 | // } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/tmp": true 9 | }, 10 | "cSpell.ignorePaths": [ 11 | "**/go/src/github.com/**", 12 | "**/usr/local/go/**", 13 | "/usr/local/go/src/**" 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "taskName": "run", 8 | "key": "shift+cmd+r", 9 | "type": "shell", 10 | "command": "go run *.go", 11 | "problemMatcher": [ 12 | "$go" 13 | ] 14 | }, 15 | { 16 | "taskName": "build", 17 | "type": "shell", 18 | "command": "go build *.go", 19 | "problemMatcher": [ 20 | "$go" 21 | ] 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 w3tecch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boilerplate for REST API in go 2 | 3 | ## Get started 4 | 5 | ### Operations 6 | 7 | | Operation | Command | Description | 8 | | --------- | ------------ | ----------- | 9 | | **serve** | `fresh *` | Runs the app and restarts if a change happened | 10 | | **run** | `go run *` | Runs the app | 11 | | **build** | `go build *` | Builds the app. | 12 | 13 | ## Librarys 14 | 15 | - **Router** [mux](http://www.gorillatoolkit.org/pkg/mux) 16 | - **Middlewares** [negroni](https://github.com/urfave/negroni) 17 | - **ORM** [gorm](http://jinzhu.me/gorm/) 18 | - **Logger** [logrus](https://github.com/sirupsen/logrus) 19 | -------------------------------------------------------------------------------- /app/config/Database.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | 8 | "github.com/jinzhu/gorm" 9 | ) 10 | 11 | var gormConn *gorm.DB 12 | 13 | // GetDatabaseConnection returns gorm connection 14 | func GetDatabaseConnection() *gorm.DB { 15 | // Check if a connection allready exists 16 | if gormConn != nil && gormConn.DB() != nil && gormConn.DB().Ping() == nil { 17 | return gormConn 18 | } 19 | 20 | // Try to connect to the database 21 | conn, err := gorm.Open(os.Getenv("DB_DIALECT"), os.Getenv("DB_CONNECTION")) 22 | if err != nil { 23 | logrus.Fatal("Could not connect to the database") 24 | } 25 | 26 | // Store the connection in package variable for furher request 27 | gormConn = conn 28 | 29 | return gormConn 30 | } 31 | -------------------------------------------------------------------------------- /app/ctrl/ApiCtrl.go: -------------------------------------------------------------------------------- 1 | package ctrl 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | 7 | "github.com/w3tecch/go-api-boilerplate/app/lib" 8 | ) 9 | 10 | // APIInfo ... 11 | type APIInfo struct { 12 | Name string `json:"name"` 13 | Version string `json:"version"` 14 | } 15 | 16 | // GetAPIInfo ... 17 | func GetAPIInfo(w http.ResponseWriter, r *http.Request) { 18 | res := lib.Response{ResponseWriter: w} 19 | res.SendOK(APIInfo{ 20 | Name: os.Getenv("API_TITLE"), 21 | Version: os.Getenv("API_VERSION"), 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /app/ctrl/UserCtrl.go: -------------------------------------------------------------------------------- 1 | package ctrl 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/w3tecch/go-api-boilerplate/app/lib" 7 | "github.com/w3tecch/go-api-boilerplate/app/models" 8 | ) 9 | 10 | // GetAllUsersHandler ... 11 | func GetAllUsersHandler(w http.ResponseWriter, req *http.Request) { 12 | res := lib.Response{ResponseWriter: w} 13 | user := new(models.User) 14 | users := user.FetchAll() 15 | res.SendOK(users) 16 | } 17 | 18 | // CreateUserHandler ... 19 | func CreateUserHandler(w http.ResponseWriter, r *http.Request) { 20 | req := lib.Request{ResponseWriter: w, Request: r} 21 | res := lib.Response{ResponseWriter: w} 22 | 23 | user := new(models.User) 24 | req.GetJSONBody(user) 25 | 26 | if err := user.Save(); err != nil { 27 | res.SendBadRequest(err.Error()) 28 | return 29 | } 30 | 31 | res.SendCreated(user) 32 | } 33 | 34 | // GetUserByIdHandler ... 35 | func GetUserByIdHandler(w http.ResponseWriter, r *http.Request) { 36 | req := lib.Request{ResponseWriter: w, Request: r} 37 | res := lib.Response{ResponseWriter: w} 38 | 39 | id, _ := req.GetVarID() 40 | user := models.User{ 41 | ID: id, 42 | } 43 | 44 | if err := user.FetchById(); err != nil { 45 | res.SendNotFound() 46 | return 47 | } 48 | 49 | res.SendOK(user) 50 | } 51 | 52 | // UpdateUserHandler ... 53 | func UpdateUserHandler(w http.ResponseWriter, r *http.Request) { 54 | req := lib.Request{ResponseWriter: w, Request: r} 55 | res := lib.Response{ResponseWriter: w} 56 | 57 | id, _ := req.GetVarID() 58 | 59 | user := new(models.User) 60 | req.GetJSONBody(user) 61 | user.ID = id 62 | 63 | if err := user.Save(); err != nil { 64 | res.SendBadRequest(err.Error()) 65 | return 66 | } 67 | 68 | res.SendOK(user) 69 | } 70 | 71 | // DeleteUserHandler ... 72 | func DeleteUserHandler(w http.ResponseWriter, r *http.Request) { 73 | req := lib.Request{ResponseWriter: w, Request: r} 74 | res := lib.Response{ResponseWriter: w} 75 | 76 | id, _ := req.GetVarID() 77 | user := models.User{ 78 | ID: id, 79 | } 80 | 81 | if err := user.Delete(); err != nil { 82 | res.SendNotFound() 83 | return 84 | } 85 | 86 | res.SendNoContent() 87 | } 88 | -------------------------------------------------------------------------------- /app/lib/BaseModel.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import "time" 4 | 5 | // BaseModel ... 6 | type BaseModel struct { 7 | ID uint `json:"id" gorm:"primary_key"` 8 | CreatedAt *time.Time `json:"createdAt, omitempty"` 9 | UpdatedAt *time.Time `json:"updatedAt, omitempty"` 10 | DeletedAt *time.Time `json:"deletedAt, omitempty" sql:"index"` 11 | } 12 | -------------------------------------------------------------------------------- /app/lib/Middleware.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // MiddlewareFunc ... 8 | type MiddlewareFunc func(h http.HandlerFunc) http.HandlerFunc 9 | 10 | // Middleware ... 11 | type Middleware func(h http.HandlerFunc) http.HandlerFunc 12 | 13 | // Use ... 14 | func Use(h http.HandlerFunc, middlewares ...Middleware) http.HandlerFunc { 15 | for _, m := range middlewares { 16 | h = m(h) 17 | } 18 | return h 19 | } 20 | -------------------------------------------------------------------------------- /app/lib/Request.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // Request ... 13 | type Request struct { 14 | Request *http.Request 15 | ResponseWriter http.ResponseWriter 16 | } 17 | 18 | // GetJSONBody ... 19 | func (r *Request) GetJSONBody(model interface{}) { 20 | decoder := json.NewDecoder(r.Request.Body) 21 | decoder.Decode(&model) 22 | } 23 | 24 | // GetVarID ... 25 | func (r *Request) GetVarID() (int, error) { 26 | vars := mux.Vars(r.Request) 27 | id, err := strconv.Atoi(vars["id"]) 28 | if err != nil { 29 | logrus.Info("Could not convert parameter id to int") 30 | http.Error(r.ResponseWriter, "Invalid id parameter", http.StatusBadRequest) 31 | return 0, err 32 | } 33 | return id, nil 34 | } 35 | -------------------------------------------------------------------------------- /app/lib/Response.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | // Response ... 9 | type Response struct { 10 | ResponseWriter http.ResponseWriter 11 | } 12 | 13 | // SendOK ... 14 | func (r *Response) SendOK(body interface{}) { 15 | setJSON(r.ResponseWriter) 16 | encodeJSON(r.ResponseWriter, body) 17 | } 18 | 19 | // SendCreated ... 20 | func (r *Response) SendCreated(body interface{}) { 21 | setJSON(r.ResponseWriter) 22 | setHTTPStatus(r.ResponseWriter, http.StatusCreated) 23 | encodeJSON(r.ResponseWriter, body) 24 | } 25 | 26 | // SendNoContent ... 27 | func (r *Response) SendNoContent() { 28 | setJSON(r.ResponseWriter) 29 | setHTTPStatus(r.ResponseWriter, http.StatusNoContent) 30 | encodeJSON(r.ResponseWriter, nil) 31 | } 32 | 33 | // SendBadRequest ... 34 | func (r *Response) SendBadRequest(message string) { 35 | http.Error(r.ResponseWriter, message, http.StatusBadRequest) 36 | } 37 | 38 | // SendNotFound ... 39 | func (r *Response) SendNotFound() { 40 | http.Error(r.ResponseWriter, "Not Found", http.StatusNotFound) 41 | } 42 | 43 | // SendNotImplemented ... 44 | func (r *Response) SendNotImplemented() { 45 | http.Error(r.ResponseWriter, "Not Implemented", http.StatusNotImplemented) 46 | } 47 | 48 | //////////////////////////////// 49 | 50 | func setHTTPStatus(w http.ResponseWriter, status int) { 51 | w.WriteHeader(status) 52 | } 53 | 54 | func setJSON(w http.ResponseWriter) { 55 | w.Header().Set("Content-Type", "application/json") 56 | } 57 | 58 | func encodeJSON(w http.ResponseWriter, body interface{}) { 59 | json.NewEncoder(w).Encode(body) 60 | } 61 | -------------------------------------------------------------------------------- /app/middlewares/Auth0Middleware.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "os" 5 | 6 | auth0middleware "github.com/w3tecch/go-auth0-middleware" 7 | ) 8 | 9 | // Auth0Middleware ... 10 | func Auth0Middleware() *auth0middleware.Auth0Middleware { 11 | return auth0middleware.New(auth0middleware.Options{ 12 | Endpoint: os.Getenv("AUTH0_BASE_URL"), 13 | ContextKey: "TokenInfo", 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /app/middlewares/CORSMiddleware.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import "github.com/rs/cors" 4 | 5 | // // CORSMiddleware ... 6 | // type CORSMiddleware struct{} 7 | 8 | // func (c *CORSMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { 9 | // next(rw, r) 10 | 11 | // rw.Header().Set("Access-Control-Allow-Origin", "*") 12 | // rw.Header().Set("Access-Control-Allow-Origin", "POST, GET, OPTIONS, PUT, DELETE") 13 | // rw.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token") 14 | // rw.Header().Set("Access-Control-Allow-Credentials", "true") 15 | // } 16 | 17 | // CORSMiddleware ... 18 | func CORSMiddleware() *cors.Cors { 19 | return cors.New(cors.Options{ 20 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /app/middlewares/JWTMiddleware.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | jwtmiddleware "github.com/auth0/go-jwt-middleware" 5 | jwt "github.com/dgrijalva/jwt-go" 6 | ) 7 | 8 | func JWTMiddleware() *jwtmiddleware.JWTMiddleware { 9 | return jwtmiddleware.New(jwtmiddleware.Options{ 10 | ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { 11 | return []byte("My Secret"), nil 12 | }, 13 | // When set, the middleware verifies that tokens are signed with the specific signing algorithm 14 | // If the signing method is not constant the ValidationKeyGetter callback can be used to implement additional checks 15 | // Important to avoid security issues described here: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ 16 | SigningMethod: jwt.SigningMethodHS256, 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /app/middlewares/LogMiddleware.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import negronilogrus "github.com/meatballhat/negroni-logrus" 4 | 5 | // LogMiddleware ... 6 | func LogMiddleware() *negronilogrus.Middleware { 7 | return negronilogrus.NewMiddleware() 8 | } 9 | -------------------------------------------------------------------------------- /app/middlewares/SecureMiddleware.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/unrolled/secure" 7 | "github.com/urfave/negroni" 8 | ) 9 | 10 | // SecureMiddleware ... 11 | func SecureMiddleware() negroni.HandlerFunc { 12 | s := secure.New(secure.Options{ 13 | AllowedHosts: []string{"ssl.example.com"}, // AllowedHosts is a list of fully qualified domain names that are allowed. Default is empty list, which allows any and all host names. 14 | HostsProxyHeaders: []string{"X-Forwarded-Hosts"}, // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request. 15 | SSLRedirect: true, // If SSLRedirect is set to true, then only allow HTTPS requests. Default is false. 16 | SSLTemporaryRedirect: false, // If SSLTemporaryRedirect is true, the a 302 will be used while redirecting. Default is false (301). 17 | SSLHost: "ssl.example.com", // SSLHost is the host name that is used to redirect HTTP requests to HTTPS. Default is "", which indicates to use the same host. 18 | SSLProxyHeaders: map[string]string{"X-Forwarded-Proto": "https"}, // SSLProxyHeaders is set of header keys with associated values that would indicate a valid HTTPS request. Useful when using Nginx: `map[string]string{"X-Forwarded-Proto": "https"}`. Default is blank map. 19 | STSSeconds: 315360000, // STSSeconds is the max-age of the Strict-Transport-Security header. Default is 0, which would NOT include the header. 20 | STSIncludeSubdomains: true, // If STSIncludeSubdomains is set to true, the `includeSubdomains` will be appended to the Strict-Transport-Security header. Default is false. 21 | STSPreload: true, // If STSPreload is set to true, the `preload` flag will be appended to the Strict-Transport-Security header. Default is false. 22 | ForceSTSHeader: false, // STS header is only included when the connection is HTTPS. If you want to force it to always be added, set to true. `IsDevelopment` still overrides this. Default is false. 23 | FrameDeny: true, // If FrameDeny is set to true, adds the X-Frame-Options header with the value of `DENY`. Default is false. 24 | CustomFrameOptionsValue: "SAMEORIGIN", // CustomFrameOptionsValue allows the X-Frame-Options header value to be set with a custom value. This overrides the FrameDeny option. Default is "". 25 | ContentTypeNosniff: true, // If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value `nosniff`. Default is false. 26 | BrowserXssFilter: true, // If BrowserXssFilter is true, adds the X-XSS-Protection header with the value `1; mode=block`. Default is false. 27 | CustomBrowserXssValue: "1; report=https://example.com/xss-report", // CustomBrowserXssValue allows the X-XSS-Protection header value to be set with a custom value. This overrides the BrowserXssFilter option. Default is "". 28 | ContentSecurityPolicy: "default-src 'self'", // ContentSecurityPolicy allows the Content-Security-Policy header value to be set with a custom value. Default is "". 29 | PublicKey: `pin-sha256="base64+primary=="; pin-sha256="base64+backup=="; max-age=5184000; includeSubdomains; report-uri="https://www.example.com/hpkp-report"`, // PublicKey implements HPKP to prevent MITM attacks with forged certificates. Default is "". 30 | ReferrerPolicy: "same-origin", // ReferrerPolicy allows the Referrer-Policy header with the value to be set with a custom value. Default is "". 31 | 32 | IsDevelopment: os.Getenv("ENVIRONMENT") == "development", //true, // This will cause the AllowedHosts, SSLRedirect, and STSSeconds/STSIncludeSubdomains options to be ignored during development. When deploying to production, be sure to set this to false. 33 | }) 34 | 35 | return negroni.HandlerFunc(s.HandlerFuncWithNext) 36 | } 37 | -------------------------------------------------------------------------------- /app/middlewares/say-something.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/w3tecch/go-api-boilerplate/app/lib" 8 | ) 9 | 10 | // SaySomething ... 11 | func SaySomething() lib.Middleware { 12 | return func(h http.HandlerFunc) http.HandlerFunc { 13 | return func(w http.ResponseWriter, r *http.Request) { 14 | fmt.Println("before wuhuuu middleware") 15 | h.ServeHTTP(w, r) 16 | fmt.Println("after wuhuuu middleware") 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/models/User.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/w3tecch/go-api-boilerplate/app/config" 8 | ) 9 | 10 | // User ... 11 | type User struct { 12 | // We could use this templates, but this does not work with this 13 | // user := models.User{ 14 | // ID: id, 15 | // } 16 | // gorm.Model `json:"-"` 17 | // lib.BaseModel 18 | 19 | ID int `json:"id" gorm:"primary_key"` 20 | CreatedAt *time.Time `json:"createdAt, omitempty"` 21 | UpdatedAt *time.Time `json:"updatedAt, omitempty"` 22 | DeletedAt *time.Time `json:"deletedAt, omitempty" sql:"index"` 23 | 24 | FirstName string `json:"firstname, omitempty" gorm:"not null; type:varchar(100)"` 25 | LastName string `json:"lastname, omitempty" gorm:"not null; type:varchar(100)"` 26 | Email string `json:"email, omitempty" gorm:"not null; type:varchar(100)"` 27 | } 28 | 29 | // TableName set User's table name to be `profiles` 30 | func (User) TableName() string { 31 | return "users" 32 | } 33 | 34 | // FetchAll ... 35 | func (u *User) FetchAll() []User { 36 | db := config.GetDatabaseConnection() 37 | 38 | var users []User 39 | db.Find(&users) 40 | 41 | return users 42 | } 43 | 44 | // FetchById ... 45 | func (u *User) FetchById() error { 46 | db := config.GetDatabaseConnection() 47 | 48 | if err := db.Where("id = ?", u.ID).Find(&u).Error; err != nil { 49 | return errors.New("Could not find the user") 50 | } 51 | 52 | return nil 53 | } 54 | 55 | // // Create ... 56 | // func (u *User) Create() error { 57 | // db := config.GetDatabaseConnection() 58 | 59 | // // Validate record 60 | // if !db.NewRecord(u) { // => returns `true` as primary key is blank 61 | // return errors.New("New records can not have primary key id") 62 | // } 63 | 64 | // if err := db.Create(&u).Error; err != nil { 65 | // return errors.New("Could not create user") 66 | // } 67 | 68 | // return nil 69 | // } 70 | 71 | // Save ... 72 | func (u *User) Save() error { 73 | db := config.GetDatabaseConnection() 74 | 75 | if db.NewRecord(u) { 76 | if err := db.Create(&u).Error; err != nil { 77 | return errors.New("Could not create user") 78 | } 79 | } else { 80 | if err := db.Save(&u).Error; err != nil { 81 | return errors.New("Could not update user") 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | 88 | // Delete ... 89 | func (u *User) Delete() error { 90 | db := config.GetDatabaseConnection() 91 | 92 | if err := db.Delete(&u).Error; err != nil { 93 | return errors.New("Could not find the user") 94 | } 95 | 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /app/router.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "github.com/w3tecch/go-api-boilerplate/app/ctrl" 6 | ) 7 | 8 | // NewRouter ... 9 | func NewRouter() *mux.Router { 10 | 11 | //Create main router 12 | mainRouter := mux.NewRouter().StrictSlash(true) 13 | mainRouter.KeepContext = true 14 | 15 | /** 16 | * meta-data 17 | */ 18 | mainRouter.Methods("GET").Path("/api/info").HandlerFunc(ctrl.GetAPIInfo) 19 | 20 | /** 21 | * /users 22 | */ 23 | // usersRouter.HandleFunc("/", l.Use(c.GetAllUsersHandler, m.SaySomething())).Methods("GET") 24 | mainRouter.Methods("GET").Path("/api/users").HandlerFunc(ctrl.GetAllUsersHandler) 25 | mainRouter.Methods("POST").Path("/api/users").HandlerFunc(ctrl.CreateUserHandler) 26 | mainRouter.Methods("GET").Path("/api/users/{id}").HandlerFunc(ctrl.GetUserByIdHandler) 27 | mainRouter.Methods("PUT").Path("/api/users/{id}").HandlerFunc(ctrl.UpdateUserHandler) 28 | mainRouter.Methods("DELETE").Path("/api/users/{id}").HandlerFunc(ctrl.DeleteUserHandler) 29 | 30 | return mainRouter 31 | } 32 | -------------------------------------------------------------------------------- /app/server.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/phyber/negroni-gzip/gzip" 5 | "github.com/urfave/negroni" 6 | "github.com/w3tecch/go-api-boilerplate/app/middlewares" 7 | ) 8 | 9 | // NewServer ... 10 | func NewServer() *negroni.Negroni { 11 | 12 | // Define the global middlewares 13 | server := negroni.New() 14 | server.Use(gzip.Gzip(gzip.DefaultCompression)) 15 | server.Use(middlewares.CORSMiddleware()) 16 | server.Use(middlewares.SecureMiddleware()) 17 | server.Use(middlewares.Auth0Middleware()) 18 | server.Use(middlewares.LogMiddleware()) 19 | 20 | // Attach app router 21 | server.UseHandler(NewRouter()) 22 | 23 | return server 24 | } 25 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3tecch/go-api-boilerplate/f5505c567222d30f84efece5a31d49bb8a524ba6/icon.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | _ "github.com/jinzhu/gorm/dialects/mysql" 9 | "github.com/joho/godotenv" 10 | "github.com/sirupsen/logrus" 11 | 12 | "github.com/w3tecch/go-api-boilerplate/app" 13 | "github.com/w3tecch/go-api-boilerplate/app/config" 14 | "github.com/w3tecch/go-api-boilerplate/app/models" 15 | ) 16 | 17 | func main() { 18 | // Loads the env variables 19 | err := godotenv.Load() 20 | if err != nil { 21 | logrus.Fatal("Error loading .env file") 22 | } 23 | 24 | // Check database connection 25 | db := config.GetDatabaseConnection() 26 | defer db.Close() 27 | 28 | // Prints the version and the address of our api to the console 29 | logrus.Info("Version is ", os.Getenv("API_VERSION")) 30 | logrus.Info("Starting Server on http://localhost:", os.Getenv("API_PORT")) 31 | 32 | // // Set log level 33 | // switch os.Getenv("LOG_LEVEL") { 34 | // case "debug": 35 | // logrus.SetLevel(logrus.ErrorLevel) 36 | // case "info": 37 | // logrus.SetLevel(logrus.ErrorLevel) 38 | // case "warn": 39 | // logrus.SetLevel(logrus.ErrorLevel) 40 | // default: 41 | // logrus.SetLevel(logrus.ErrorLevel) 42 | // } 43 | 44 | // Creates the database schema 45 | migrateDatabase() 46 | 47 | // Server router on given port and attach the cors headers 48 | server := app.NewServer() 49 | log.Fatal(http.ListenAndServe(":"+os.Getenv("API_PORT"), server)) 50 | } 51 | 52 | func migrateDatabase() { 53 | db := config.GetDatabaseConnection() 54 | 55 | // Migrate the given tables 56 | db.AutoMigrate(&models.User{}) 57 | 58 | } 59 | --------------------------------------------------------------------------------