Golang Rest API with Sqlite and Jwt

Victor Yeo
4 min readJul 10, 2022

--

In this article, we will create a Rest API using golang. The Rest API will have HTTP GET, POST and PATCH endpoints. The root path will return a html template. The Rest API supports CRUD operations on sqlite database. Additionally, there is a login endpoint which creates and returns a Jwt token, and a protected endpoint which requires the said Jwt token. Besides that, there is a simple test case which performs unit test on the root path.

Firstly, we create a new folder (can be any name) and add the bare minimum code to the main.go file:

package mainimport (
“github.com/gin-gonic/gin”
)
func main() {
fmt.Print("Code is ", " starting.\n")
router := gin.Default()
router.Run("localhost:9090")
}

This “gin-gonic/gin” is a golang web framework. In this article, we use Gin to route requests, retrieve request details, and add JSON responses.

We run go mod init to initialise the project, and go get .to get the project dependencies.

After that, in main.go, we add the Rest API route.

router.GET(“/”, getRoot)
router.GET(“/todos”, getTodos)
router.GET(“/todos/:id”, getTodo)
router.PATCH(“/todos/:id”, toggleTodoStatus)
router.POST(“/todos”, addTodo)

We also add a customised “no route” error message.

router.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{“code”: “PAGE_NOT_FOUND”, “message”: “Page not found”})
})

An example of the Http Post “/todos” Rest API is shown below. The data in gin context is passed to newTodo.

func addTodo(context *gin.Context) {
var newTodo todo
if err := context.BindJSON(&newTodo); err != nil {
return
}
todos = append(todos, newTodo)
context.IndentedJSON(http.StatusCreated, newTodo)
}

Next, we create a connection to sqlite database. We use “gorm.io/gorm” ORM library for connecting to sqlite.

We create a folder models/, and add setup.go file to setup the database connection.

import (
“gorm.io/driver/sqlite”
“gorm.io/gorm”
)
var DB *gorm.DB
func ConnectDatabase() {
database, err := gorm.Open(sqlite.Open(“test.db”), &gorm.Config{})
if err != nil {
panic(“Failed to connect to database!”)
}
database.AutoMigrate(&Book{})
DB = database
}

We create a folder controllers/, and add book.go file to handle the books related Rest API.

As an example, the function to handle HTTP GET Rest API to retrieve all books is shown below.

func FindBooks(c *gin.Context) {
var books []models.Book
models.DB.Find(&books)
c.JSON(http.StatusOK, gin.H{“data”: books})
}

In main.go, add the database connection code and the Rest API endpoints.

models.ConnectDatabase()
router.GET(“/books”, controllers.FindBooks)
router.GET("/books/:id", controllers.FindBook)

Next, we add the code to load html template.

Create a folder templates/, add html files to the folder, such as index.html shown below.

<! — index.html →<! — Embed the header.html template at this location →
{{ template “header.html” .}}
<h1>Hello World!</h1><! — Embed the footer.html template at this location →
{{ template “footer.html” .}}

In main.go, we add the following line:

router.LoadHTMLGlob(“templates/*”)

Next, we create the code to handle Jwt token creation and validation. We create the login API endpoint in main.go:

router.POST(“/login”, login)

We add the login function. The Jwt token is created using jwt.NewWithClaims, with the passed in username parameter, and using SigningMethodHS256 as signing method. Then the Jwt token is signed with a secret key to create the token string.

func login(c *gin.Context) {
loginParams := loginst{}
c.ShouldBindJSON(&loginParams)
fmt.Print(“Login params “, loginParams, “\n”)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
“user”: loginParams.Username,
“nbf”: time.Date(2018, 01, 01, 12, 0, 0, 0, time.UTC).Unix(),
})
tokenStr, err := token.SignedString([]byte(middleware.GetSecretKey())) if err != nil {
c.JSON(http.StatusInternalServerError, models.UnsignedResponse{
Message: err.Error(),
})
return
}
c.JSON(http.StatusOK, models.SignedResponse{
Token: tokenStr,
Message: “logged in”,
})
return
}

Create a folder middleware/, add the file JWTmiddleware.go. As an example, we look at the parseToken function. The function calls jwt Parse to parse the Jwt token, and calls token.Method.(*jwt.SigningMethodHMAC) to validate the signing algo.

func parseToken(jwtToken string) (*jwt.Token, error) {
token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
if _, OK := token.Method.(*jwt.SigningMethodHMAC); !OK {
return nil, errors.New(“bad signed method received”)
}
return []byte(GetSecretKey()), nil
})
if err != nil {
return nil, errors.New(“bad jwt token”)
}
return token, nil
}

We add a protected API endpoint to main.go. To access the protected API, we need to supply Jwt Token to the authorization header in the http call.

privateRouter := router.Group(“/private”)privateRouter.Use(middleware.JWTTokenCheck)privateRouter.GET(“/test/:uid”, testPrivate)

The testPrivate is just a simple function.

func testPrivate(c *gin.Context) {
uidStr := c.Param(“uid”)
if uidStr != “” {
c.JSON(200, gin.H{“uid”: uidStr})
return
}
c.JSON(200, gin.H{“error”: “unknown uid”})
}

For the unit test, we need to setup the test environment. I create a file common_test.go and add the function below to it.

func TestMain(m *testing.M) {
//Set Gin to Test Mode
gin.SetMode(gin.TestMode)
// Run the other tests
os.Exit(m.Run())
}

Then, i create another file main_test.go, and add the unit test function to it.

func TestShowIndexPageUnauthenticated(t *testing.T) {
r := getRouter(true)
r.GET(“/”, getRoot)
// Create a request to send to the above route
req, _ := http.NewRequest(“GET”, “/”, nil)
testHTTPResponse(t, r, req, func(w *httptest.ResponseRecorder) bool {
// Test that the http status code is 200
statusOK := w.Code == http.StatusOK
// Test that the page title is “Home Page”
p, err := ioutil.ReadAll(w.Body)
pageOK := err == nil && strings.Index(string(p), “<title>Home Page</title>”) > 0
return statusOK && pageOK
})
}

That’s it. This is the end of the article. The github repo is available at
https://github.com/victoryeo/golang-restapi

--

--

No responses yet