How to create a basic RESTful API in Go


Like any languages, it is possible to quickly code a basic RESTful API with Golang. In this example, datas will be accessible via standards HTTP methods (GET, POST, PUT & DELETE) in JSON format. Data rows will be storaged in a SQLite file.

Schema

Routes

Table “users”

  • id — integer & auto-increment
  • firstname — varchar(255)
  • lastname — varchar(255)

Gin for routes

We’re gonna use Gin who is a micro framework routing. It’s friendly user and fast.

go get github.com/gin-gonic/gin

Ok, let’s start coding ! Firstly in a new “main.go” file, we call our libraries.

package main

import (
"strconv"

"github.com/gin-gonic/gin"
)

Secondly, we declare the structure “User” :

type Users struct {
Id int `gorm:"AUTO_INCREMENT" form:"id" json:"id"`
Firstname string `gorm:"not null" form:"firstname" json:"firstname"`
Lastname string `gorm:"not null" form:"lastname" json:"lastname"`
}

The “gorm” param will be used later, with the database connection…

Thirdly, in the main() function, we regoup our routes in a single group :

func main() {
r := gin.Default()

v1 := r.Group("api/v1")
{
v1.POST("/users", PostUser)
v1.GET("/users", GetUsers)
v1.GET("/users/:id", GetUser)
v1.PUT("/users/:id", UpdateUser)
v1.DELETE("/users/:id", DeleteUser)
}

r.Run(":8080")
}

Then we declare the five functions calling in the routes :

func PostUser(c *gin.Context) {
// The futur code…
}

func GetUsers(c *gin.Context) {
var users = []Users{
Users{Id: 1, Firstname: "Oliver", Lastname: "Queen"},
Users{Id: 2, Firstname: "Malcom", Lastname: "Merlyn"},
}

c.JSON(200, users)

// curl -i http://localhost:8080/api/v1/users
}

func GetUser(c *gin.Context) {
id := c.Params.ByName("id")
user_id, _ := strconv.ParseInt(id, 0, 64)

if user_id == 1 {
content := gin.H{"id": user_id, "firstname": "Oliver", "lastname": "Queen"}
c.JSON(200, content)
} else if user_id == 2 {
content := gin.H{"id": user_id, "firstname": "Malcom", "lastname": "Merlyn"}
c.JSON(200, content)
} else {
content := gin.H{"error": "user with id#" + id + " not found"}
c.JSON(404, content)
}

// curl -i http://localhost:8080/api/v1/users/1
}

func UpdateUser(c *gin.Context) {
// The futur code…
}

func DeleteUser(c *gin.Context) {
// The futur code…
}

At this stage, let’s start our API server with the classic command :

go run main.go

As you can see, for reading users, our URL’s (GET) are working good with fake datas.

For all users :

[{"id":1,"firstname":"Oliver","lastname":"Queen"},{"id":2,"firstname":"Malcom","lastname":"Merlyn"}]

For a user :

{"firstname":"Oliver","id":1,"lastname":"Queen"}

SQLite with the ORM Gorm

go get github.com/jinzhu/gorm

With Gorm, we’re gonna use a SQLite database. You can also use MySQL (and MariaDB), Postgres et FoundationDB database instead.

go get github.com/mattn/go-sqlite3

Your compiler will be angry if you forgot to import the new librairies (and we don’t need the “strconv” library).

import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)

No need to create the database file and the table “users” your-self, Gorm does the job for you. ☺

func InitDb() *gorm.DB {
// Openning file
db, err := gorm.Open("sqlite3", "./data.db")
db.LogMode(true)
    // Error
if err != nil {
panic(err)
}
    // Creating the table
if !db.HasTable(&Users{}) {
db.CreateTable(&Users{})
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&Users{})
}

return db
}

As agreed, at the next restart of your server, the table “users” will be created with his fields in the futur file “data.db”.

CRUD

Create a user

func PostUser(c *gin.Context) {
db := InitDb()
defer db.Close()

var user Users
c.Bind(&user)

if user.Firstname != "" && user.Lastname != "" {
// INSERT INTO "users" (name) VALUES (user.Name);
db.Create(&user)
// Display error
c.JSON(201, gin.H{"success": user})
} else {
// Display error
c.JSON(422, gin.H{"error": "Fields are empty"})
}

// curl -i -X POST -H "Content-Type: application/json" -d "{ \"firstname\": \"Thea\", \"lastname\": \"Queen\" }" http://localhost:8080/api/v1/users
}

After the CURL command, the file “data.db” is created :).

Result :

HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{"success":{"id":1,"firstname":"Thea","lastname":"Queen"}}

Read all users

func GetUsers(c *gin.Context) {
// Connection to the database
db := InitDb()
// Close connection database
defer db.Close()

var users []Users
// SELECT * FROM users
db.Find(&users)

// Display JSON result
c.JSON(200, users)

// curl -i http://localhost:8080/api/v1/users
}

Result :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[{"id":1,"firstname":"Thea","lastname":"Queen"}]

Read a user

func GetUser(c *gin.Context) {
// Connection to the database
db := InitDb()
// Close connection database
defer db.Close()

id := c.Params.ByName("id")
var user Users
// SELECT * FROM users WHERE id = 1;
db.First(&user, id)

if user.Id != 0 {
// Display JSON result
c.JSON(200, user)
} else {
// Display JSON error
c.JSON(404, gin.H{"error": "User not found"})
}

// curl -i http://localhost:8080/api/v1/users/1
}

Result :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"firstname":"Thea","lastname":"Queen"}

Update a user

func UpdateUser(c *gin.Context) {
// Connection to the database
db := InitDb()
// Close connection database
defer db.Close()

// Get id user
id := c.Params.ByName("id")
var user Users
// SELECT * FROM users WHERE id = 1;
db.First(&user, id)

if user.Firstname != "" && user.Lastname != "" {

if user.Id != 0 {
var newUser Users
c.Bind(&newUser)

result := Users{
Id: user.Id,
Firstname: newUser.Firstname,
Lastname: newUser.Lastname,
}

// UPDATE users SET firstname='newUser.Firstname', lastname='newUser.Lastname' WHERE id = user.Id;
db.Save(&result)
// Display modified data in JSON message "success"
c.JSON(200, gin.H{"success": result})
} else {
// Display JSON error
c.JSON(404, gin.H{"error": "User not found"})
}

} else {
// Display JSON error
c.JSON(422, gin.H{"error": "Fields are empty"})
}

// curl -i -X PUT -H "Content-Type: application/json" -d "{ \"firstname\": \"Thea\", \"lastname\": \"Merlyn\" }" http://localhost:8080/api/v1/users/1
}

Result :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"success":{"id":1,"firstname":"Thea","lastname":"Merlyn"}}

Delete a user

func DeleteUser(c *gin.Context) {
// Connection to the database
db := InitDb()
// Close connection database
defer db.Close()

// Get id user
id := c.Params.ByName("id")
var user Users
// SELECT * FROM users WHERE id = 1;
db.First(&user, id)

if user.Id != 0 {
// DELETE FROM users WHERE id = user.Id
db.Delete(&user)
// Display JSON result
c.JSON(200, gin.H{"success": "User #" + id + " deleted"})
} else {
// Display JSON error
c.JSON(404, gin.H{"error": "User not found"})
}

// curl -i -X DELETE http://localhost:8080/api/v1/users/1
}

Result :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"success":"User #1 deleted"}

CORS

You can use CORS directly in the concern route.

c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
c.Next()

Or globally with a custom middleware in a function with “gin.HandlerFunc”:

func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
c.Next()
}
}

And before routes call :

r.Use(Cors())

If you don’t activated CORS, you will get message error like this with Chrome :

XMLHttpRequest cannot load http://localhost:8080/api/v1/users. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8081' is therefore not allowed access.

OPTIONS

If you are using “XMLHttpRequest” or “Fetch” with Javacript and CORS, you will need to use “OPTIONS” for requests POST, PUT, DELETE.

Firstly, you must add 2 routes.

v1.OPTIONS("/users", OptionsUser)      // POST
v1.OPTIONS("/users/:id", OptionsUser) // PUT, DELETE

And declare the “OptionsUser” function.

func OptionsUser(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Methods", "DELETE,POST, PUT")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
c.Next()
}

If you don’t using this method you will get a message error like this with Chrome :

XMLHttpRequest cannot load http://localhost:8080/api/v1/users/1. Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8081' is therefore not allowed access. The response had HTTP status code 404.

In fact, the navigator does not find the OPTIONS http://localhost:8080/api/v1/users/1 URL

Conclusion

We have an example of a simple and functionnal basic RESTful API. As you can see, structures are importants in Golang, especialy when you manipulate some Json datas.

Have a good fun with Go ;)

More informations about :