In this article, we're going to create a simple REST APIs with complete CRUD functionality using Golang. Also, we will be using Gorilla Mux router for routing purposes. What is Golang?
Golang is a statically typed, compiled programming language designed at Google by Robert Griesemer, Rob Pike, and Ken Thompson.
It is syntactically similar to C, but with memory safety, exceptional garbage collection, structural typing, and concurrency.
The language is often referred to as Golang because of its domain name,
golang.org
, but the proper name is Go.
What are we building?
we are building an API that will allow us to create, update, delete and view the coding courses.
INSTALLATION:
If you are reading this article, then you might be having Golang installed on your machine already, but in case if you don't, go ahead and download it from golang.org/dl.
For a complete and in-depth installation guide, watch the following video. This article is mainly focused on designing RESTful APIs using Go and not the installation.
I would suggest Windows users to use git bash as their terminal because windows cmd is not that efficient. If you don't have git installed on your machine, go ahead and download it from git-scm.com/downloads.
LET'S CREATE OUR FOLDER STRUCTURE:
In golang, it is not necessary to create a folder structure. However, it's a good habit to organize your code as if you will publish it someday.
Open up your git bash and run following command in it:
mkdir -p $GOPATH/src/github.com/<your_github_username>/restapi
Now, if you go to C:\Users\USERNAME\go\src\github.com\<your_github_username>
you should see a folder named restapi
. That's where we have to store our code files.
If you don't find C:\Users\USERNAME\go
that means, you have to set up your GOPATH
.
Run the following command in you git bash to do that:
export GOPATH=$HOME/go
and again run following command:
mkdir -p $GOPATH/src/github.com/<your_github_username>/restapi
It will create a restapi
folder in C:\Users\USERNAME\go\src\github.com\<your_github_username>
This should be your folder structure:
C:/
- USERNAME/
- go/
- src/
- github.com/
- <your_github_username>/
- restapi/
Go ahead and open the restapi
folder in VS code.
MAKE GIT BASH THE DEFAULT TERMINAL OF VS CODE:
Instead of using git bash externally, you can make git bash as "the default terminal" of your VS code. Follow these two steps: (git should be installed in your machine)
Click on Select Default Shell as shown below: It will pop up a list of available terminals. Select Git Bash from it, as shown below:
LET'S DESIGN THE REST APIS:
Create a new file named main.go
.
If you are in VS code, as soon as you create
main.go
, you will get a pop up to install some packages related to go. So, go ahead and click on install all. It will take some time to install those. (ignore if you've already installed them)
Install Gorilla Mux Router by running the following command:
go get -u github.com/gorilla/mux
In main.go
initialize the main package and import necessary packages, including the gorilla mux:
package main
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
Let's create a main function. In that, initialize the mux router and the server using net/http
package that we've imported.
func main() {
//Initialize router using mux
router := mux.NewRouter()
// Create the routes, we will be creating each function in future
router.HandleFunc("/api/courses", getCourses).Methods("GET")
router.HandleFunc("/api/course/{id}", getSingleCourse).Methods("GET")
router.HandleFunc("/api/courses/create", createCourse).Methods("POST")
router.HandleFunc("/api/courses/update/{id}", updateCourse).Methods("PUT")
router.HandleFunc("/api/courses/delete/{id}", deleteCourse).Methods("DELETE")
// Initialize a server, log.Fatal will throw an error if anything goes wrong
log.Fatal(http.ListenAndServe(":8000", router))
}
Let's create our models outside the main function using Struct, which will hold information about our data (It's like a model which we define in Django/Express)
// Author model (Struct)
type Author struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
// Course model (Struct)
type Course struct {
ID string `json:"id"`
Name string `json:"name"`
Price string `json:"price"`
Link string `json:"link"`
Author *Author `json:"author"` // This field is pointing towards the Author struct
}
If you take a look at the above code, in Course
Struct we are defining a field Name
of a type string
, which will appear as name
when we get a JSON
response. That's how we are mapping fields in the struct to hold our data.
What is Struct in Go?
Structs are collections of fields with each field having its type. They’re useful for grouping data together, to form records like we've done here to group our
Course
andAuthor
models.
LET'S ADD A DUMMY DATA WHICH WILL ACT AS A DATABASE:
// Initialize courses variable as a slice of type struct Course (which nothing but an Array data structure)
var courses []Course
func main() {
//Initialize router
router := mux.NewRouter()
// Dummy data of the books, as we don't have a database (slice)
// we are just appending a Course struct (which we are creating on the spot)
// in 'courses' slice (array) that we've defined earlier
// syntax --> append(<where you want to append>,<what you want to append>)
courses = append(courses, Course{ID: "124134", Name: "FullStack Django Developer Freelance ready", Price: "299", Link: "https://courses.learncodeonline.in/learn/FullStack-Django-Developer-Freelance-ready",
Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
courses = append(courses, Course{ID: "154434", Name: "Full stack with Django and React", Price: "299", Link: "https://courses.learncodeonline.in/learn/Full-stack-with-Django-and-React",
Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
courses = append(courses, Course{ID: "198767", Name: "Complete React Native bootcamp", Price: "199", Link: "https://courses.learncodeonline.in/learn/Complete-React-Native-Mobile-App-developer",
Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
// Handel the routes (when you are making a request from postman, make sure that you are hitting correct url, even missing or extra "/" can give an error
router.HandleFunc("/api/courses", getCourses).Methods("GET")
router.HandleFunc("/api/course/{id}", getSingleCourse).Methods("GET")
router.HandleFunc("/api/courses/create", createCourse).Methods("POST")
router.HandleFunc("/api/courses/update/{id}", updateCourse).Methods("PUT")
router.HandleFunc("/api/courses/delete/{id}", deleteCourse).Methods("DELETE")
// Initialize a server
log.Fatal(http.ListenAndServe(":8000", router))
}
LET'S CREATE THE FUNCTIONS FOR EACH ROUTE:
Function to get all the Courses:
// function to get all the books
// this is identical to app.get("/",(req,res)=>{}) in the express.js
func getCourses(res http.ResponseWriter, req *http.Request) {
// we have to set the header "Content-Type: application/json"
// because we are sending JSON data with a request through postman
res.Header().Set("Content-Type", "application/json")
// we are taking variable 'courses' in which we've appended dummy data and returning that as a response
json.NewEncoder(res).Encode(courses)
}
This is getCourses
function, which we're executing when the user hits localhost:8000/api/courses
.
Run the server by running the following commands: (Whenever we make changes in our code, we have to kill the existing server and run these two commands)
go build
#go build will create .exe file of name restapi
./restapi
If there is nothing in the terminal, that means our server is running. So, let's see the response of the above function:
Set Content-Type
as application/json
in Postman's Headers as shown below (This header is important when we are sending POST
and PUT
requests):
Let's hit localhost:8000/api/courses
and see what we get:
As you can see, we've got all the courses
(in JSON
format) from our dummy data.
Function to get a single Course:
// function to get a single course
func getSingleCourse(res http.ResponseWriter, req *http.Request) {
// set the header "Content-Type: application/json"
res.Header().Set("Content-Type", "application/json")
params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url
// we are looping through courses and
// if a course with ID == 'id' that we've passed in url
// we are encoding that course only
for _, item := range courses {
if item.ID == params["id"] {
json.NewEncoder(res).Encode(item) // sending matched course in json format
return
}
}
// if we don't get any matching course we will simply return a message
json.NewEncoder(res).Encode("No course found")
}
This is getSingleCourse
function, which we are executing when the user hits localhost:8000/api/course/{id}
As we have changed our code, we have to kill the existing server and run the server again by running the following commands:
go build
#go build will create .exe file of name restapi
./restapi
Let's hit localhost:8000/api/course/{id}
and see what we get:
As you can see, after hitting localhost:8000/api/course/124134
it returned course
with an id
124134
. Let's see if we hit the url
with the wrong id
what we get!
It returned "No course found".
Function to create a Course:
func createCourse(res http.ResponseWriter, req *http.Request) {
// set the header "Content-Type: application/json"
res.Header().Set("Content-Type", "application/json")
// asign a course variable of type Course struct
var course Course
// take everyhing that is coming from postman
// which is nothing but req.Body. Store the decoded req.Body in 'course' variable
_ = json.NewDecoder(req.Body).Decode(&course)
course.ID = strconv.Itoa(rand.Intn(1000000)) // just creating dummy id as we are not sending an id from postman - not safe for production
// Now, we will simply append that course with decoded req.Body in our
// courses slice (Array) which we have defined] earlier
courses = append(courses, course)
// At the end, send the course that we have created.
json.NewEncoder(res).Encode(course)
}
This is createCourse
function, which we are executing when the user hits localhost:8000/api/courses/create
with POST
request.
Kill the existing server and run the server again:
go build && ./restapi
This is a POST
request, Which means we are going to send some JSON
data to create a course. So, let's hit localhost:8000/api/courses/create
with POST
request and see what we get:
Now, let's hit localhost:8000/api/courses
to get all the courses and see if we get the newly created course in the response:
Here it is!
Function to delete a Course:
func deleteCourse(res http.ResponseWriter, req *http.Request) {
// set the header "Content-Type: application/json"
res.Header().Set("Content-Type", "application/json")
params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url
// looping through all the courses we have
// then compare each course's ID with the id that we've passed in the url (params)
for i, item := range courses {
if item.ID == params["id"] {
// This is slicing of a slice(array)
// append all the courses in `courses` slice (array) except, the one which has ID equal to the id which we've passed in the url
// for example, if ID of the course, whose index is "3" is equal to the id we've passed in the url.
// we will take items from index "0-2" and "4-`last index`" i.e. except index "3" from `courses` slice(array)
// and will store that slice(array) in courses itself
courses = append(courses[:i], courses[i+1:]...)
break
}
}
json.NewEncoder(res).Encode(courses) // it will return all the other courses except the deleted one.
}
This is deleteCourse
function, which we are executing when the user hits localhost:8000/api/courses/delete/{id}
with DELETE
request.
Kill the existing server and run the server again:
go build && ./restapi
Let's hit localhost:8000/api/courses/delete/{id}
with DELETE
request and see what we get:
Now, let's hit localhost:8000/api/courses
to get all the courses and see if we get the deleted course in the response:
Function to update a Course:
func updateCourse(res http.ResponseWriter, req *http.Request) {
// set the header "Content-Type: application/json"
res.Header().Set("Content-Type", "application/json")
params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url
// this method is bit cheeky because we don't have a database
for i, item := range courses {
if item.ID == params["id"] {
// this is the same slicing technique that we used to delete the course in deleteCourse() func
courses = append(courses[:i], courses[i+1:]...)
var course Course
// take data which is coming from postman
// and create new course from it
_ = json.NewDecoder(req.Body).Decode(&course)
course.ID = params["id"] // we are keeping the id same because we are updating the existing course and not creating new one - not safe for production
// Now, append that course in 'courses' slice(array)
courses = append(courses, course)
json.NewEncoder(res).Encode(course)
return
}
}
}
This is updateCourse
function, which we are executing when the user hits localhost:8000/api/courses/update/{id}
with PUT
request.
Kill the existing server and run the server again:
go build && ./restapi
Let's hit localhost:8000/api/courses/update/{id}
with PUT
request and see what we get:
We got the response with changed name
and changed price
as we expected.
Now, let's hit localhost:8000/api/courses
to get all the courses and see if we get the updated course in the response:
HERE IS FULL main.go
FILE:
package main
import (
"encoding/json"
"log"
"math/rand"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// Author model (Struct)
type Author struct {
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
// Course model (Struct)
type Course struct {
ID string `json:"id"`
Name string `json:"name"`
Price string `json:"price"`
Link string `json:"link"`
Author *Author `json:"author"`
}
// Initialize Course variable as slice (which nothing but an Array data structure)
var courses []Course
// function to get all the books
func getCourses(res http.ResponseWriter, req *http.Request) {\
res.Header().Set("Content-Type", "application/json")
json.NewEncoder(res).Encode(courses)
}
// function to get a single course
func getSingleCourse(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
params := mux.Vars(req)
for _, item := range courses {
if item.ID == params["id"] {
json.NewEncoder(res).Encode(item) // we are sending matched course in json format
return
}
}
json.NewEncoder(res).Encode("No course found")
}
func createCourse(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
var course Course
_ = json.NewDecoder(req.Body).Decode(&course)
course.ID = strconv.Itoa(rand.Intn(1000000))
courses = append(courses, course)
json.NewEncoder(res).Encode(course)
}
func updateCourse(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url
for i, item := range courses {
if item.ID == params["id"] {
courses = append(courses[:i], courses[i+1:]...)
var course Course
_ = json.NewDecoder(req.Body).Decode(&course)
course.ID = params["id"]
courses = append(courses, course)
json.NewEncoder(res).Encode(course)
return
}
}
}
func deleteCourse(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
params := mux.Vars(req) // we are extracting 'id' of the Course which we are passing in the url
for i, item := range courses {
if item.ID == params["id"] {
courses = append(courses[:i], courses[i+1:]...)
break
}
}
json.NewEncoder(res).Encode(courses) // it will return all the other courses except the deleted one.
}
func main() {
//Initialize router
router := mux.NewRouter()
// Dummy data of books as we don't have a database (slice)
courses = append(courses, Course{ID: "124134", Name: "FullStack Django Developer Freelance ready", Price: "299", Link: "https://courses.learncodeonline.in/learn/FullStack-Django-Developer-Freelance-ready",
Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
courses = append(courses, Course{ID: "154434", Name: "Full stack with Django and React", Price: "299", Link: "https://courses.learncodeonline.in/learn/Full-stack-with-Django-and-React",
Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
courses = append(courses, Course{ID: "198767", Name: "Complete React Native bootcamp", Price: "199", Link: "https://courses.learncodeonline.in/learn/Complete-React-Native-Mobile-App-developer",
Author: &Author{Firstname: "Hitesh", Lastname: "Choudhary"}})
// Handel the routes
router.HandleFunc("/api/courses", getCourses).Methods("GET")
router.HandleFunc("/api/course/{id}", getSingleCourse).Methods("GET")
router.HandleFunc("/api/courses/create", createCourse).Methods("POST")
router.HandleFunc("/api/courses/update/{id}", updateCourse).Methods("PUT")
router.HandleFunc("/api/courses/delete/{id}", deleteCourse).Methods("DELETE")
// Initialize a server
log.Fatal(http.ListenAndServe(":8000", router))
}
With this, we come to an end of this article on Building REST API with Golang.
If you face any problem during this project, you can go ahead and check out the code on my Github.
Also, make sure to subscribe to our newsletter on blog.learncodeonline.in and never miss any upcoming articles related to programming just like this one.
I hope this post will help you in your journey. Keep learning!