This is the first part of this series on Golang for Web. My goal is to show the newcomers how easily we can build REST APIs with Go by breaking down big apps into small ones. Here, we're going to build a simple TODO app webserver with Gofiber by separating all routes, controllers, etc.
Go: An open-source programming language
Go is an open-source programming language that makes it easy to build simple, reliable, and efficient software.
Go is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson. Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency. (Source: Wikipedia)
Golang for Web 🌐
As a MERN stack developer, I found Fiber Web Framework is very similar to express as they are claiming and this is pretty easy for a JS developer to get started with Golang to build a nice and amazing REST API.
Fiber is an Express inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. Designed to ease things up for fast development with zero memory allocation and performance in mind.
Check their amazing benchmark performance here - https://docs.gofiber.io/extra/benchmarks
What will we build? 🤔
Okay enough talk 🤚 It's time to show some action with Fiber. Today we are going to build a basic TODO app with Fiber. I'm pretty sure after this, you can easily start building REST APIs with Golang 👨🏻💻
Prerequisites 📝
- A basic knowledge of Golang syntax.
- Go version 1.14 or above installed on your machine. Install from here
- Postman or any other related app installed on your machine. Download from here
If you have these, Let's get started 🚀
Let’s Begin 🏁
1. Setup our Project
First, let’s create a new directory named fiber-todo
and open this directory in VS Code (or any other Code Editor/ IDE)
Now at the root directory open your terminal and run the command :
go mod init github.com/<Your_Username>/<Repo_Name>
Example :
go mod init github.com/devsmranjan/golang-fiber-basic-todo-app
Let's install fiber in our project by running :
go get -u github.com/gofiber/fiber/v2
Now inside our root directory create a file named main.go
and create two directories routes
& controllers
.
2. Create our first server
Now inside main.go
write the following code :
package main
import (
"github.com/gofiber/fiber/v2"
)
func main() {}
Okay, you will get some errors here but stay tuned with me 🤝
Now inside the main()
method let's initiate fiber.
app := fiber.New()
Add our first route by :
app.Get("/", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the endpoint 😉",
})
})
and finally, listen to the server at 8000 port and catch the error if any.
err := app.Listen(":8000")
if err != nil {
panic(err)
}
Here is our final code :
package main
import (
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
// give response when at /
app.Get("/", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the endpoint 😉",
})
})
// Listen on server 8000 and catch error if any
err := app.Listen(":8000")
// handle error
if err != nil {
panic(err)
}
}
Now to run our server, open terminal and in our root directory run :
go run main.go
It will show something like this
┌───────────────────────────────────────────────────┐
│ Fiber v2.2.0 │
│ http://127.0.0.1:8000 │
│ │
│ Handlers ............. 2 Threads ............. 4 │
│ Prefork ....... Disabled PID ............. 60983 │
└───────────────────────────────────────────────────┘
Open your browser now, and goto localhost:8000
. You will get the output like this
{
"message": "You are at the endpoint 😉",
"success": true
}
Hureeey!!! We did it 🤟
Additionally, if you want to add logger middleware, then run
go get -u github.com/gofiber/fiber/v2/middleware/logger
Import logger in our project
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger" // new
)
and finally, connect this middleware with our app
app.Use(logger.New())
Here is our final code :
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger" // new
)
func main() {
app := fiber.New()
app.Use(logger.New()) // new
// give response when at /
app.Get("/", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the endpoint 😉",
})
})
// Listen on server 8000 and catch error if any
err := app.Listen(":8000")
// handle error
if err != nil {
panic(err)
}
}
Now after running our server again, goto localhost:8000
and you can see the terminal is showing the log of our request like below :
21:44:48 | 200 | 0s | 127.0.0.1 | GET | /
3. Decide Endpoints for our TODO app 🔗
localhost:8000/api/todos
request: GET
description: To get all todos
localhost:8000/api/todos/:id
request: GET
description: Get todo by id
localhost:8000/api/todos
request: POST
input: {
title : String
}
description: Create new todo
localhost:8000/api/todos/:id
request: PUT
input: {
title : String,
completed : Boolean
}
description: Update todo
localhost:8000/api/todos/:id
request: DELETE
description: Delete todo
4. Build our first API endpoint 🏗
Step 1 :
Let's write our first controller for our todo app.
Open controllers
directory and create a file named todo.go
Now inside controllers/todo.go
let's do our required imports.
package controllers
import (
"github.com/gofiber/fiber/v2"
)
Add Todo
structure
type Todo struct {
Id int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
Let's add some predefined todos
var todos = []*Todo{
{
Id: 1,
Title: "Walk the dog 🦮",
Completed: false,
},
{
Id: 2,
Title: "Walk the cat 🐈",
Completed: false,
},
}
and now finally let's create our controller to get all the todos
func GetTodos(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todos": todos,
},
})
}
Here is our final code in controllers/todo.go
:
package controllers
import (
"github.com/gofiber/fiber/v2"
)
type Todo struct {
Id int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
var todos = []*Todo{
{
Id: 1,
Title: "Walk the dog 🦮",
Completed: false,
},
{
Id: 2,
Title: "Walk the cat 🐈",
Completed: false,
},
}
// get all todos
func GetTodos(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todos": todos,
},
})
}
Step 2 :
Now let's write our first route for our todo app and connect our controllers.
Open the routes
directory and create a file named todo.go
Now inside routes/todo.go
let's do our required imports
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/devsmranjan/golang-fiber-basic-todo-app/controllers" // replace
)
Replace github.com/devsmranjan/golang-fiber-basic-todo-app/controllers
with your github repo url like github.com/<Your_Username>/<Repo_Name>/controllers
Create our first route to get all todos
func TodoRoute(route fiber.Router) {
route.Get("", controllers.GetTodos)
}
Here is our final code in routes/todo.go
:
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/devsmranjan/golang-fiber-basic-todo-app/controllers" // replace
)
func TodoRoute(route fiber.Router) {
route.Get("", controllers.GetTodos)
}
Step 3 :
Okay here is the final step.
Go back to main.go
and import routes
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/devsmranjan/golang-fiber-basic-todo-app/routes" // new // replace
)
Replace github.com/devsmranjan/golang-fiber-basic-todo-app/routes
with your github repo url i.e github.com/<Your_Username>/<Repo_Name>/routes
Now let's create a separate function to handle all our routes.
func setupRoutes(app *fiber.App) {}
Move all routes from main()
method to setupRoutes()
method.
func setupRoutes(app *fiber.App) {
// moved from main method
app.Get("/", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the endpoint 😉",
})
})
}
Now call setupRoutes()
method from main()
method.
setupRoutes(app)
Here is our final main()
looks like.
func main() {
app := fiber.New()
app.Use(logger.New())
// setup routes
setupRoutes(app) // new
// Listen on server 8000 and catch error if any
err := app.Listen(":8000")
// handle error
if err != nil {
panic(err)
}
}
Now let's make a routes group named api
inside our setupRoutes()
method
api := app.Group("/api")
Add response for /api
route
api.Get("", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the api endpoint 😉",
})
})
Now finally, connect all the todo routes to our api
route group
routes.TodoRoute(api.Group("/todos"))
Final setupRoutes()
method looks like :
func setupRoutes(app *fiber.App) {
// give response when at /
app.Get("/", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the endpoint 😉",
})
})
// api group
api := app.Group("/api")
// give response when at /api
api.Get("", func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"message": "You are at the api endpoint 😉",
})
})
// connect todo routes
routes.TodoRoute(api.Group("/todos"))
}
Now let's run our server and go to http://localhost:8000/api/todos
. The output will be like below.
{
"data": {
"todos": [
{
"id": 1,
"title": "Walk the dog 🦮",
"completed": false
},
{
"id": 2,
"title": "Walk the cat 🐈",
"completed": false
}
]
},
"success": true
}
5. Let's create other controllers 🎛
I guess you got the idea, how all are working. Right??? 😎
Now let's create other controllers.
But before that let's add some required imports in controllers/todo.go
:
import (
"fmt" // new
"strconv" // new
"github.com/gofiber/fiber/v2"
)
Okay. Let's create a controller to create a todo
func CreateTodo(c *fiber.Ctx) error {
type Request struct {
Title string `json:"title"`
}
var body Request
err := c.BodyParser(&body)
// if error
if err != nil {
fmt.Println(err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse JSON",
})
}
// create a todo variable
todo := &Todo{
Id: len(todos) + 1,
Title: body.Title,
Completed: false,
}
// append in todos
todos = append(todos, todo)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todo": todo,
},
})
}
A controller to get a single todo by id
func GetTodo(c *fiber.Ctx) error {
// get parameter value
paramId := c.Params("id")
// convert parameter value string to int
id, err := strconv.Atoi(paramId)
// if error in parsing string to int
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse Id",
})
}
// find todo and return
for _, todo := range todos {
if todo.Id == id {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todo": todo,
},
})
}
}
// if todo not available
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Todo not found",
})
}
A controller to update a todo
func UpdateTodo(c *fiber.Ctx) error {
// find parameter
paramId := c.Params("id")
// convert parameter string to int
id, err := strconv.Atoi(paramId)
// if parameter cannot parse
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse id",
})
}
// request structure
type Request struct {
Title *string `json:"title"`
Completed *bool `json:"completed"`
}
var body Request
err = c.BodyParser(&body)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse JSON",
})
}
var todo *Todo
for _, t := range todos {
if t.Id == id {
todo = t
break
}
}
if todo.Id == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Not found",
})
}
if body.Title != nil {
todo.Title = *body.Title
}
if body.Completed != nil {
todo.Completed = *body.Completed
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todo": todo,
},
})
}
A controller to delete a todo
func DeleteTodo(c *fiber.Ctx) error {
// get param
paramId := c.Params("id")
// convert param string to int
id, err := strconv.Atoi(paramId)
// if parameter cannot parse
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse id",
})
}
// find and delete todo
for i, todo := range todos {
if todo.Id == id {
todos = append(todos[:i], todos[i+1:]...)
return c.Status(fiber.StatusNoContent).JSON(fiber.Map{
"success": true,
"message": "Deleted Succesfully",
})
}
}
// if todo not found
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Todo not found",
})
}
Here is our final code in controllers/todo.go
:
package controllers
import (
"fmt"
"strconv"
"github.com/gofiber/fiber/v2"
)
type Todo struct {
Id int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
var todos = []*Todo{
{
Id: 1,
Title: "Walk the dog 🦮",
Completed: false,
},
{
Id: 2,
Title: "Walk the cat 🐈",
Completed: false,
},
}
// get all todos
func GetTodos(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todos": todos,
},
})
}
// Create a todo
func CreateTodo(c *fiber.Ctx) error {
type Request struct {
Title string `json:"title"`
}
var body Request
err := c.BodyParser(&body)
// if error
if err != nil {
fmt.Println(err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse JSON",
})
}
// create a todo variable
todo := &Todo{
Id: len(todos) + 1,
Title: body.Title,
Completed: false,
}
// append in todos
todos = append(todos, todo)
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todo": todo,
},
})
}
// get a single todo
// PARAM: id
func GetTodo(c *fiber.Ctx) error {
// get parameter value
paramId := c.Params("id")
// convert parameter value string to int
id, err := strconv.Atoi(paramId)
// if error in parsing string to int
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse Id",
})
}
// find todo and return
for _, todo := range todos {
if todo.Id == id {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todo": todo,
},
})
}
}
// if todo not available
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Todo not found",
})
}
// Update a todo
// PARAM: id
func UpdateTodo(c *fiber.Ctx) error {
// find parameter
paramId := c.Params("id")
// convert parameter string to int
id, err := strconv.Atoi(paramId)
// if parameter cannot parse
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse id",
})
}
// request structure
type Request struct {
Title *string `json:"title"`
Completed *bool `json:"completed"`
}
var body Request
err = c.BodyParser(&body)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse JSON",
})
}
var todo *Todo
for _, t := range todos {
if t.Id == id {
todo = t
break
}
}
if todo.Id == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Not found",
})
}
if body.Title != nil {
todo.Title = *body.Title
}
if body.Completed != nil {
todo.Completed = *body.Completed
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"success": true,
"data": fiber.Map{
"todo": todo,
},
})
}
// Delete a todo
// PARAM: id
func DeleteTodo(c *fiber.Ctx) error {
// get param
paramId := c.Params("id")
// convert param string to int
id, err := strconv.Atoi(paramId)
// if parameter cannot parse
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Cannot parse id",
})
}
// find and delete todo
for i, todo := range todos {
if todo.Id == id {
todos = append(todos[:i], todos[i+1:]...)
return c.Status(fiber.StatusNoContent).JSON(fiber.Map{
"success": true,
"message": "Deleted Succesfully",
})
}
}
// if todo not found
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"success": false,
"message": "Todo not found",
})
}
6. Create other routes 🚏
Let's create other routes for our todo app.
Now, inside TodoRoute()
add all below routes
route.Post("", controllers.CreateTodo)
route.Put("/:id", controllers.UpdateTodo)
route.Delete("/:id", controllers.DeleteTodo)
route.Get("/:id", controllers.GetTodo)
Finally, our final code looks like :
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/devsmranjan/golang-fiber-basic-todo-app/controllers" // replace
)
func TodoRoute(route fiber.Router) {
route.Get("", controllers.GetTodos)
route.Post("", controllers.CreateTodo) // new
route.Put("/:id", controllers.UpdateTodo) // new
route.Delete("/:id", controllers.DeleteTodo) // new
route.Get("/:id", controllers.GetTodo) // new
}
Now we are all set to run our server 😎
And let's test our API in Postman.
7. Test our endpoints 🧪
To get all todos, give a GET
request to localhost:8000/api/todos
To get todo by id, give a GET
request to localhost:8000/api/todos/:id
Here replace :id
with a todo id
To create a new todo, give a POST
request to localhost:8000/api/todos
with a title: <String>
in the request body.
Okay. Let's check if our todo is successfully created or not.
Give a GET
request to localhost:8000/api/todos
to get all todos again.
Yeeeeeeeeah !!! 🤩
Now, let's update a todo by giving a PUT
request to localhost:8000/api/todos/:id
with a title: <String>
or completed: <Boolean>
or both in the request body.
Here replace :id
with a todo id
To delete a todo, give a DELETE
request to localhost:8000/api/todos/:id
Here replace :id
with a todo id
Congooooooo 🥳 🥳 🥳 We did it 💪🏻
Conclusion 📋
For more information, I suggest taking a deeper look at the documentation here https://docs.gofiber.io/
Here is my GitHub link for this project - https://github.com/devsmranjan/golang-fiber-basic-todo-app
Thank you for reading my article 🙂 . I hope you have learned something here.
Happy coding 👨💻👩💻 and stay tuned for my next post in this series!
Thanks! Don't forget to give a ♥️ and follow :)
Top comments (6)
The thing that I always miss with this kind of explanations are the middle ware to secure the endpoints as now everyone can call them and create havoc
This is the first part of the
Golang for Web
series. Here I want to show, how thisfiber
web framework works with Golang.We will go step by step. We'll add a database in our next part of this series with this app and then we will go for Authentication to make our endpoints private and so on.
Stay tuned. 🤝
Thank you for posting this. Any idea when will you post the second part?
Well written with nice explanations... Seems very similar to the
echo
router that I have been using are you aware of why I would choosefiber
overecho
?I've not tried
echo
yet. But if you'll check the benchmark score offiber
, it seems pretty good thanecho
.Take a look here - docs.gofiber.io/extra/benchmarks
Thank you so much for posting this blog about Fiber. This is very helpful. In my project I am using Firestore. Can you please guide me how can I connect to Firestore?