DEV Community

Janire Fernandez
Janire Fernandez

Posted on • Edited on

Create a REST API with Go

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
Enter fullscreen mode Exit fullscreen mode

It should print the Go language version that you have.
Otherwise install the Go language before proceeding.

brew install go
Enter fullscreen mode Exit fullscreen mode

Create a new Go project

First, create a new directory for the Go project and go into the directory created.

mkdir articleRestApi

cd articleRestApi
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And for google/uuid run

go get github.com/google/uuid
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

If you now check http://localhost:8080/ you should see message Welcome to the Article REST API!

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
Enter fullscreen mode Exit fullscreen mode

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"`
}
Enter fullscreen mode Exit fullscreen mode

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"},
}
Enter fullscreen mode Exit fullscreen mode

Now our project looks like this:

├── cmd
│   └── main.go
├── pkg
│    ├── mocks
│    │   └── article.go
│    └── models
│        └── article.go
├── go.sum
└── go.mod
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

If we now run main.go we can do the request to get all the articles.

go run cmd/main.go
Enter fullscreen mode Exit fullscreen mode

*Note: It will return the articles defined in pkg/mocks/article.go.

GetArticles Paw

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

If we now run main.go we can do the request to get an specific article by id.

go run cmd/main.go
Enter fullscreen mode Exit fullscreen mode

GetArticle Paw

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")
}
Enter fullscreen mode Exit fullscreen mode

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))
}

Enter fullscreen mode Exit fullscreen mode

If we now run main.go we can do the request to add a new article.

Add Article Paw

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

If we now run main.go we can do the request to update an article.

Update Article Paw

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

If we now run main.go we can do the request to delete an article.

DeleteArticle Paw

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
Enter fullscreen mode Exit fullscreen mode

Improvements

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)

Collapse
 
y3ovem4 profile image
y3ovem4

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!

Image description

Collapse
 
y3ovem4 profile image
y3ovem4

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.