golang api

How to Create RESTful API with Golang?

Programming

 

In this tutorial, I’ve explained: “How to create REST API with Go?” Let’s assume that you want to create a school API. And our resources:

  • classes
  • students
  • teachers

RESTful API Object/Resource Details

As I mentioned, we’ve 3 different resource types. These resources are different and they have different features. So, firstly I should create object models.

Class Student Teacher
-id
-name
-max_size
-student_count
-id
-name
-class
-teachers
-id
-name
-num_of_students
api design
Endpoint Endpoint URI Method What does endpoint do? Returns
CreateStudent /students POST Creates a new student resource(obj) 201 CREATED {message, student_id}
404 NOT FOUND {message, code}
ListStudents /students GET List all of the student objets 200 OK {[]student}
404 NOT FOUND {message, code}
GetStudent /students/id/{id} GET Get the specified student 200 OK {student}
404 NOT FOUND {message, code}
ListClasses /classes GET List all of the class objs 200 OK {[]class}
404 NOT FOUND {message, code}
CreateClass /classes POST Creates a new class obj 201 CREATED {message, class_id}
404 NOT FOUND {message, code}
GetClass /classes/id/{id} GET Get the specified class 200 OK {class}
404 NOT FOUND {message, code}
CreateTeacher /teachers POST Creates a new teacher obj 201 CREATED {message, teacher_id}
404 NOT FOUND {message, code}
ListTeachers /teachers GET List all of the teachers 200 OK {[]class}
404 NOT FOUND {message, code}
GetTeacher /teachers/id/{id} GET Get the specified teacher 200 OK {teacher}
404 NOT FOUND {message, code}
💡
Query String Params can be used for search API purposes.
/students?teachers=1,2,3

Code the API

After API design stage, let’s code. Firstly, I’ve created main.go file and then, I should create structs for our resources.

package main

type Student struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Class    string `json:"class"`
	Teachers string `json:"teachers"`
}

type Teacher struct {
	ID              string `json:"id"`
	Name            string `json:"name"`
	Num_of_Students string `json:"num_of_students"`
}

type Class struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Max_size      int    `json:"max_size"`
	Student_count int    `json:"student_count"`
}

Then, let’s implement our main function to serve API as a service. To serve a localhost server I’ve used Golang’s Gin library. I’ve also implemented some of the endpoints.

func main() {
	router := gin.Default()                 // Router is our server.
	router.GET("/students", listStudents)   // List all student objects
	router.POST("/students", createStudent) // -> Create new student

	router.Run("localhost:9090")

}

As you can see in the above code, I’ve used “listStudents” and “createStudents” functions to handle GET and POST requests that cames “/students” endpoint. So, I’ve created this functions:

func createStudent(context *gin.Context) {
	var student Student
	err := context.BindJSON(&student)

	// Create student obj with given json value (if there is no err)
	if err == nil && student.ID != "" && student.Name != "" && student.Class != "" && student.Teachers != "" {
		students = append(students, student)
		// Create custom response if created
		context.IndentedJSON(http.StatusCreated, gin.H{"message": "Obj created", "student_id": student.ID})
		return
	} else {
		// Create custom response if not created
		context.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Object cannot created!"})
		return
	}
}

func listStudents(context *gin.Context) {
	context.IndentedJSON(http.StatusOK, students)
}

I’ve created a simple data to see on Postman.

var students = []Student{
	{ID: "1", Name: "John Doe", Class: "1-b", Teachers: "1,2,4"},
}

All of the source code until this stage:

package main

import (
	"net/http"

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

var students = []Student{
	{ID: "1", Name: "John Doe", Class: "1-b", Teachers: "1,2,4"},
}

type Student struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Class    string `json:"class"`
	Teachers string `json:"teachers"`
}

type Teacher struct {
	ID              string `json:"id"`
	Name            string `json:"name"`
	Num_of_Students string `json:"num_of_students"`
}

type Class struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Max_size      int    `json:"max_size"`
	Student_count int    `json:"student_count"`
}

func createStudent(context *gin.Context) {
	var student Student
	err := context.BindJSON(&student)

	// Create student obj with given json value (if there is no err)
	if err == nil && student.ID != "" && student.Name != "" && student.Class != "" && student.Teachers != "" {
		students = append(students, student)
		// Create custom response if created
		context.IndentedJSON(http.StatusCreated, gin.H{"message": "Obj created", "student_id": student.ID})
		return
	} else {
		// Create custom response if not created
		context.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Object cannot created!"})
		return
	}
}

func listStudents(context *gin.Context) {
	context.IndentedJSON(http.StatusOK, students)
}

func main() {
	router := gin.Default()                 // Router is our server.
	router.GET("/students", listStudents)   // List all student objects
	router.POST("/students", createStudent) // -> Create new student

	router.Run("localhost:9090")

}

With this code, when you run your program with go run main.go you can use /students endpoints with POST and GET methods. As you can see in the following screenshot I am able to list students with the GET method.

api design postman

I am able to add new student with the POST method and fulfilled JSON values. (id, name, class, and teachers must have value. Cannot be empty. I’ve checked them with an if block in createStudent function. E.g: student.ID != "")

postman request

request api

rest api request
Getting API Objects with ID

To get a certain API object we can use URI parameters. For example, if you want to get an object that has ID=1 you can use “GET /students/1”.

Let’s code this mechanism… In the following code snippet I’ve implemented getStudent and getStudentByID functions.

func getStudentByID(id string) (*Student, error) {
	for i, s := range students {
		if s.ID == id {
			return &students[i], nil
		}
	}

	return nil, errors.New("Student not found!")
}

func getStudent(context *gin.Context) {
	id := context.Param("id")
	fmt.Println("idddd: " + id)

	student, err := getStudentByID(id)
	if err != nil {
		context.IndentedJSON(http.StatusNotFound, gin.H{"message": "Student not found!"})
		return
	}

	context.IndentedJSON(http.StatusOK, student)
}
  • getStudent(): This function parses the URI context and gets its ID param. Then sends the value of this param to getStudentByID function.
  • getStudentByID(): This function iterates all of the elements in students array and looks for the student object that is given by ID. It searchs student object that matches ID came from getStudent function.

I’ve also added a new line to controle /students/<ID> endpoint requests.

router.GET("/students/:id", getStudent) // Get the specified student with ID

Final Source Code (Until here):

package main

import (
	"errors"
	"fmt"
	"net/http"

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

var students = []Student{
	{ID: "1", Name: "John Doe", Class: "1-b", Teachers: "1,2,4"},
}

type Student struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Class    string `json:"class"`
	Teachers string `json:"teachers"`
}

type Teacher struct {
	ID              string `json:"id"`
	Name            string `json:"name"`
	Num_of_Students string `json:"num_of_students"`
}

type Class struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Max_size      int    `json:"max_size"`
	Student_count int    `json:"student_count"`
}

func createStudent(context *gin.Context) {
	var student Student
	err := context.BindJSON(&student)

	// Create student obj with given json value (if there is no err)
	if err == nil && student.ID != "" && student.Name != "" && student.Class != "" && student.Teachers != "" {
		students = append(students, student)
		// Create custom response if created
		context.IndentedJSON(http.StatusCreated, gin.H{"message": "Obj created", "student_id": student.ID})
		return
	} else {
		// Create custom response if not created
		context.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Object cannot created!"})
		return
	}
}

func listStudents(context *gin.Context) {
	context.IndentedJSON(http.StatusOK, students)
}

func getStudentByID(id string) (*Student, error) {
	for i, s := range students {
		if s.ID == id {
			return &students[i], nil
		}
	}

	return nil, errors.New("Student not found!")
}

func getStudent(context *gin.Context) {
	id := context.Param("id")
	fmt.Println("idddd: " + id)

	student, err := getStudentByID(id)
	if err != nil {
		context.IndentedJSON(http.StatusNotFound, gin.H{"message": "Student not found!"})
		return
	}

	context.IndentedJSON(http.StatusOK, student)
}

func main() {
	router := gin.Default()                 // Router is our server.
	router.GET("/students", listStudents)   // List all student objects
	router.POST("/students", createStudent) // Create new student
	router.GET("/students/:id", getStudent) // Get the specified student with ID

	router.Run("localhost:9090")

}

Finally, you can find all of the source code in https://github.com/rbozburun/GoRestAPIExercises/blob/main/main-SimpleSchoolAPI.go file.

This API is not well optimized, I will optimize and update this post with a new post 🙂

In this article, we’ve developed REST API with Go. Golang. has perfect libraries to create RESTful APIs and other back-end services…

Leave a Reply