In this example we will create a simple REST API with Go.
- Get all articles.
- Get specific article.
- Add new article.
- Update Article.
- Delete article.
Let's get started!
Install Go
Make sure that Go language is installed in your system. Open a new terminal and run the following command.
go version
It should print the Go language version that you have.
Otherwise install the Go language before proceeding.
brew install go
Create a new Go project
First, create a new directory for the Go project and go into the directory created.
mkdir articleRestApi
cd articleRestApi
Now we need to create the project module with the go mod init <path>
command.
The module path normally is the URL where you publish your project. In my case it will be github.com/janirefdez/ArticleRestApi
go mod init github.com/janirefdez/ArticleRestApi
This command will create a file called go.mod
. It will contain information about the Go module path and the Go language version. When adding more dependencies to the project, they will be mentioned in this file.
Set up
In this example we are going to use a gorilla/mux router instead of the traditional net/http router.
And we are also going to use google/uuid to generate random uuids.
Run the following command to be able to use gorilla/mux
go get github.com/gorilla/mux
And for google/uuid
run
go get github.com/google/uuid
You should see that in go.mod
it should appear the dependencies gorilla/mux
and google/uuid
. And a new file go.sum
should have been created.
Create the base for our API
Now let's create the main.go
file.
touch main.go
and add the following code to it.
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the Article REST API!")
fmt.Println("Article REST API")
}
func handleRequests() {
// create a new instance of a mux router
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/", homePage)
log.Fatal(http.ListenAndServe(":8080", myRouter))
}
func main() {
handleRequests()
}
This has:
- A homePage function that will handle all requests to our root URL.
- A handleRequests function that will match the URL path hit with a defined function
- A main function which will kick off our REST API.
Let's run our API to see what happens ;)!
go run main.go
If you now check http://localhost:8080/
you should see message Welcome to the Article REST API!
This means we have now successfully created the base for our REST API.
But first we need to organize the project because we don't want to code everything on main.go
. So let's move main.go
to a new directory called cmd
. At this point the project structure should look like this.
├── cmd
│ └── main.go
├── go.sum
└── go.mod
Create Article Model and Mock
Now we need to define our Article model. In this example, Article will contain an Id
, a Title
, a Description
and Content
.
Let's create file pkg/models/article.go
.
package models
type Article struct {
Id string `json:"Id"`
Title string `json:"Title"`
Desc string `json:"desc"`
Content string `json:"content"`
}
and also create mocks for that Article model pkg/mocks/article.go
.
package mocks
import "github.com/janirefdez/ArticleRestApi/pkg/models"
var Articles = []models.Article{
{Id: "8617bf49-39a9-4268-b113-7b6bcd189ba2", Title: "Article 1", Desc: "Article Description 1", Content: "Article Content 1"},
{Id: "38da7ce2-02b5-471a-90b8-c299f2ef132e", Title: "Article 2", Desc: "Article Description 2", Content: "Article Content 2"},
}
Now our project looks like this:
├── cmd
│ └── main.go
├── pkg
│ ├── mocks
│ │ └── article.go
│ └── models
│ └── article.go
├── go.sum
└── go.mod
API EndPoints
Now we are going to start creating our API endpoints.
Get All Articles
This will be an HTTP GET
request that will return all the articles.
Create a new handler pkg/handlers/GetAllArticles.go
.
package handlers
import (
"encoding/json"
"net/http"
"github.com/janirefdez/ArticleRestApi/pkg/mocks"
)
func GetAllArticles(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(mocks.Articles)
}
The call to json.NewEncoder(w).Encode(mocks.Articles)
does the job of encoding our articles array into a JSON string and then writing as part of our response.
In function handleRequests()
from cmd/main.go
include the new endpoint.
func handleRequests() {
// create a new instance of a mux router
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/", homePage)
myRouter.HandleFunc("/articles", handlers.GetAllArticles).Methods(http.MethodGet)
log.Fatal(http.ListenAndServe(":8080", myRouter))
}
If we now run main.go
we can do the request to get all the articles.
go run cmd/main.go
*Note: It will return the articles defined in pkg/mocks/article.go
.
Get Article By Id
This will be an HTTP GET
request that will return one article.
Create a new handler pkg/handlers/GetArticle.go
.
package handlers
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/janirefdez/ArticleRestApi/pkg/mocks"
)
func GetArticle(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
for _, article := range mocks.Articles {
if article.Id == id {
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(article)
break
}
}
}
And in function handleRequests()
from cmd/main.go
include the new endpoint.
func handleRequests() {
// create a new instance of a mux router
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/", homePage)
myRouter.HandleFunc("/articles", handlers.GetAllArticles).Methods(http.MethodGet)
myRouter.HandleFunc("/articles/{id}", handlers.GetArticle).Methods(http.MethodGet)
log.Fatal(http.ListenAndServe(":8080", myRouter))
}
If we now run main.go
we can do the request to get an specific article by id.
go run cmd/main.go
Create new Article
This will be an HTTP POST
request that will add a new article.
Create a new handler pkg/handlers/AddArticle.go
.
package handlers
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"github.com/janirefdez/ArticleRestApi/pkg/mocks"
"github.com/janirefdez/ArticleRestApi/pkg/models"
"github.com/google/uuid"
)
func AddArticle(w http.ResponseWriter, r *http.Request) {
// Read to request body
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatalln(err)
}
var article models.Article
json.Unmarshal(body, &article)
article.Id = (uuid.New()).String()
mocks.Articles = append(mocks.Articles, article)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode("Created")
}
In this function we are reading the information of the article the client has sent. And we are generating a random uuid to that article.
If everything goes well, we send response HTTP 201.
*Note: 201 -> created success status response code indicates that the request has succeeded and has led to the creation of a resource.
And in function handleRequests()
from cmd/main.go
include the new endpoint.
func handleRequests() {
// create a new instance of a mux router
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/", homePage)
myRouter.HandleFunc("/articles", handlers.GetAllArticles).Methods(http.MethodGet)
myRouter.HandleFunc("/articles/{id}", handlers.GetArticle).Methods(http.MethodGet)
myRouter.HandleFunc("/articles", handlers.AddArticle).Methods(http.MethodPost)
log.Fatal(http.ListenAndServe(":8080", myRouter))
}
If we now run main.go
we can do the request to add a new article.
If you then do the request to get all articles, the new article you have added should be there.
Update Article by id
This will be an HTTP PUT
request that will update an article.
Create a new handler pkg/handlers/UpdateArticle.go
.
package handlers
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/janirefdez/ArticleRestApi/pkg/mocks"
"github.com/janirefdez/ArticleRestApi/pkg/models"
)
func UpdateArticle(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
// Read request body
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatalln(err)
}
var updatedArticle models.Article
json.Unmarshal(body, &updatedArticle)
for index, article := range mocks.Articles {
if article.Id == id {
article.Title = updatedArticle.Title
article.Desc = updatedArticle.Desc
article.Content = updatedArticle.Content
mocks.Articles[index] = article
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode("Updated")
break
}
}
}
And in function handleRequests()
from cmd/main.go
include the new endpoint.
func handleRequests() {
// create a new instance of a mux router
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/", homePage)
myRouter.HandleFunc("/articles", handlers.GetAllArticles).Methods(http.MethodGet)
myRouter.HandleFunc("/articles/{id}", handlers.GetArticle).Methods(http.MethodGet)
myRouter.HandleFunc("/articles", handlers.AddArticle).Methods(http.MethodPost)
myRouter.HandleFunc("/articles/{id}", handlers.UpdateArticle).Methods(http.MethodPut)
log.Fatal(http.ListenAndServe(":8080", myRouter))
}
If we now run main.go
we can do the request to update an article.
If you then do the request to get all articles, the article should be updated.
Delete Article by id
This will be an HTTP DELETE
request that will delete an article.
Create a new handler pkg/handlers/DeleteArticle.go
.
package handlers
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/janirefdez/ArticleRestApi/pkg/mocks"
)
func DeleteArticle(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
for index, article := range mocks.Articles {
if article.Id == id {
mocks.Articles = append(mocks.Articles[:index], mocks.Articles[index+1:]...)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode("Deleted")
break
}
}
}
And in function handleRequests()
from cmd/main.go
include the new endpoint.
func handleRequests() {
// create a new instance of a mux router
myRouter := mux.NewRouter().StrictSlash(true)
myRouter.HandleFunc("/", homePage)
myRouter.HandleFunc("/articles", handlers.GetAllArticles).Methods(http.MethodGet)
myRouter.HandleFunc("/articles/{id}", handlers.GetArticle).Methods(http.MethodGet)
myRouter.HandleFunc("/articles", handlers.AddArticle).Methods(http.MethodPost)
myRouter.HandleFunc("/articles/{id}", handlers.UpdateArticle).Methods(http.MethodPut)
myRouter.HandleFunc("/articles/{id}", handlers.DeleteArticle).Methods(http.MethodDelete)
log.Fatal(http.ListenAndServe(":8080", myRouter))
}
If we now run main.go
we can do the request to delete an article.
If you then do the request to get all articles, the article you deleted shouldn't appear.
Now our project looks like this:
├── cmd
│ └── main.go
├── pkg
│ ├── handlers
│ │ ├── AddArticle.go
│ │ ├── DeleteArticle.go
│ │ ├── GetAllArticles.go
│ │ ├── GetArticle.go
│ │ └── UpdateArticle.go
│ ├── mocks
│ │ └── article.go
│ └── models
│ └── article.go
├── go.sum
└── go.mod
Improvements
- Include error response when something goes wrong.
- Include error response when an article is not found.
- Connect the API to a real database
- ...
But this is a simple example to learn how to create a REST API. Hope you enjoyed!!
If you want to check the whole project here you have the link: ArticleRestApi
In the next example we will connect this API to PostgreSQL database.
Don't forget to like and share! Thank you! :)
Top comments (2)
Hi,
First of all,
Thank you for the detailed tutorial, it helped me a lot.
I tried the APIs and it worked well except for the AddArticle.
When I tried to create a new article with the POST API,
I still got the same response HTTP 200 instead of HTTP 201,
just like I was calling the GET API GetAllArticles.
Did I do something wrong?
Thanks for your help again!
I tried to print something different in the GET API GetAllArticles,
and called the POST API AddArticle.
I realized that it is excatly the situation that when I call the POST API , it's the GET API being called instead.
I tried to change the API url to "/articles/post" , and it finally works!!!
I wonder why the API GetArticle , UpdateArticle and DeleteArticle can use the mutual url "article/{id}";
however GetAllArticle and AddArticle cannot.