The purpose of this post is to provide instructions on how to setup a simple RESTful API server, in Golang, using only the net/http package and not any other third-party web framework. You will learn how to create REST endpoints within your project that can handle POST, GET, PUT and DELETE HTTP requests. This is the first post in a multi-post series.
This post belongs to the following series:
- How to Build an API Server in Go - Part 1: Basic Server
- How to Build an API Server in Go - Part 2: Simple Database
- How to Build an API Server in Go - Part 3: Postgres Database
- How to Build an API Server in Go - Part 4: Access Control
Why net/http
Standard Library?
A general popular opinion in web-development is to use a web-development framework for build web application - why reinvent the wheel?
However in Golang, this general popular opinion is not held firm. The standard library is the core package provided by Golang is sufficient for all our web application needs.
I have used (and am using) go-chi
and fibre
web libraries and I would highly recommend both. For this article, I want to explore what’s involved with writing an application without any external library and only the provided http
library.
What do I need?
- You will need Golang version 1.15+ installed on your development machine.
- You will need to know the basics of Golang. I am assuming you’ve finished learning about how Golang works and you are looking to learn how to start coding a webserver for a API restful purposes.
Requirements - What are we building?
Let’s build a web-application which allows users to handle time-series data. The user can do the following:
- Create a new time-series datum
- List time-series data
- Retrieve a single time-series datum
- Update a single timer-series datum
- Delete a single time-series datum
The name of our application will be called mulberry
and it will exist in a code repository mulberry-server
.
And remember, for this article, we cannot use any framework!
Design - How will it work?
Project Structure
In Golang, there is no official project structure; as a result, the onus is on the developer to structure a correct project hierarchy. A popular solution developers choose is the following convention. Our application’s initial structure will look as follows (and will grow with time!):
📦mulberry-server
│ 📄README.md
│ 📄Makefile
│
└───📁cmd
│ |
| └───📁serve
| 📄main.go
│
└───📁internal
│
└───📁controllers
📄controller.go
API Endpoints
We will have the following API endpoints:
|----------------------------------------------------------------------------
| METHOD | URL | DESCRIPTION |
|----------------------------------------------------------------------------
| GET | /api/v1/version | Get the application version |
| POST | /api/v1/login | Authenticate the user |
| POST | /api/v1/register | Creates an account for a user |
| GET | /api/v1/time-series-data | List all the time-series data |
| POST | /api/v1/time-series-data | Create a time-series datum |
| GET | /api/v1/time-series-datum/<uuid> | Get the details of a datum |
| PUT | /api/v1/time-series-datum/<uuid> | Update a single datum |
| DELETE | /api/v1/time-series-datum/<uuid> | Delete a single datum |
|----------------------------------------------------------------------------
Implementation - Let’s build our code!
1. Project Location
Start in your default Golang home folder:
$ cd ~/go/src/github.com/bartmika
Please note:
- Replace
bartmika
with your username on GitHub. - If that folder does not exist, please create it now.
2. Code repository
Create a new code repository called mulberry-server
and clone your code repository:
$ git clone https://github.com/bartmika/mulberry-server.git
$ cd mulberry-server
Please note:
- Replace
bartmika
with your username on GitHub.
3. Golang Modules
Since we are using Golang modules please follow these steps to get started:
$ go mod init github.com/bartmika/mulberry-server
$ export GIT_TERMINAL_PROMPT=1
$ go mod tidy
After running the above code your repository should look like this:
📦mulberry-server
📄go.md
4. Project Scaffolding
Before we write any API related functionality, let’s structure our code with a good base to build from. The scaffolding we use is based on the “model-view-controller” pattern; in addition, the structure is my opinion and should not be considered the standard.
Start by creating the folders and the files inside each folder.
This is how the main.go file will look like.
|
|
And the controller.go file looks like this:
|
|
That’s it, we’ve structured our project which will grow nicely.
Let us learn how to create our first API endpoint and then call it. We will be implementing the /api/v1/version
URL path which supports GET requests.
Create a new file called version.go with the contents of:
|
|
Next update the controller.go with the following:
|
|
Finally start the server and confirm everything works:
$ go run cmd/serve/main.go;
If you see a message saying the server started then congratulations, you have setup the project scaffolding!
Testing - Verify the code works
Before you begin, please install the httpie
application. Once installed, please run the following code to confirm we can make an API call:
$ http get 127.0.0.1:5000/api/v1/version
If you get a result somewhat similar like this, then congratulations! Please note if you are using Windows with Git bash for Windows
and it hangs, then please visit this url for a solution.
Last login: Thu Jan 28 00:10:01 on ttys015
bmika@MACMINI-AFA2131 mulberry-server % http get 127.0.0.1:5000/api/v1/version
HTTP/1.1 200 OK
Content-Length: 6
Content-Type: application/json
Date: Fri, 29 Jan 2021 05:03:22 GMT
Mulberry Server v1.0
If you are not using httpie
you can use curl
. Here is what you would write in your terminal:
$ curl -X GET \
-H "Content-type: application/json" \
-H "Accept: application/json" \
"http://127.0.0.1:5000/api/v1/version"
Implement remaining API endpoints
Before we begin, you have to be aware of an issue, the issue is that the std net/http
does not add any support for URL Arguments such as /api/v1/time-series-datum/:uuid
. As a result, Golang developers are torn between using a third-party package, structuring your API endpoints differently, or writing your own custom router.
For more on this topic, please see the following links:
- Go’s std net/http is all you need … right?
- How to not use an http-router in go
- Different approaches to HTTP routing in Go
Given these options, what should we do? From the beginners perspective, the split switch seems like the easiest to do, in addition it’s quite performant with the cost of slightly ugly looking code.
Before we begin, make sure your project hierarchy look as follows by creating the tsd.go file:
📦mulberry-server
│ 📄README.md
│ 📄Makefile
│
└───📁cmd
│ |
| └───📁serve
| 📄main.go
│
└───📁internal
│
└───📁controllers
📄controller.go
📄version.go
📄tsd.go
📄user.go
Please fill the tsd.go file with the following contents:
|
|
And then create the user.go file:
|
|
And then update the controller.go file:
|
|
And finally to test out making API calls, we have the following commands you can run in your console.
$ http get 127.0.0.1:5000/api/v1/version
$ http post 127.0.0.1:5000/api/v1/register email="lalal@lalal.com" password="lalalal" name="lalalalalala"
$ http post 127.0.0.1:5000/api/v1/login email="lalal@lalal.com" password="lalalal"
$ http get 127.0.0.1:5000/api/v1/time-series-data
$ http post 127.0.0.1:5000/api/v1/time-series-data instrument_id="1" timestamp="2021-01-01"
$ http get 127.0.0.1:5000/api/v1/time-series-datum/xxx
$ http put 127.0.0.1:5000/api/v1/time-series-datum/xxx instrument_id="1" timestamp="2021-01-02"
$ http delete 127.0.0.1:5000/api/v1/time-series-datum/xxx
If you got a 200 OK
response with some-sort of string message, then congratulations! You have implemented your server using only the net/http
standard library.
If you want to use the curl
command then run the following:
$ curl -X GET \
-H "Content-type: application/json" \
-H "Accept: application/json" \
"http://127.0.0.1:5000/api/v1/version"
$ curl -X POST \
-H "Content-type: application/json" \
-H "Accept: application/json" \
-d '{"email":"lalal@lalal.com","password":"lalalal"}' \
"http://127.0.0.1:5000/api/v1/login"
$ curl -X POST \
-H "Content-type: application/json" \
-H "Accept: application/json" \
-d '{"email":"lalal@lalal.com","password":"lalalal","name":"lalalal"}' \
"http://127.0.0.1:5000/api/v1/register"
$ curl -X GET \
-H "Content-type: application/json" \
-H "Accept: application/json" \
"http://127.0.0.1:5000/api/v1/time-series-data"
$ curl -X POST \
-H "Content-type: application/json" \
-H "Accept: application/json" \
-d '{"instrument_id":1,"timestamp":"2021-01-01"}' \
"http://127.0.0.1:5000/api/v1/time-series-data"
$ curl -X GET \
-H "Content-type: application/json" \
-H "Accept: application/json" \
"http://127.0.0.1:5000/api/v1/time-series-datum/xxx"
$ curl -X PUT \
-H "Content-type: application/json" \
-H "Accept: application/json" \
-d '{"instrument_id":1,"timestamp":"2021-01-01"}' \
"http://127.0.0.1:5000/api/v1/time-series-datum/xxx"
$ curl -X DELETE \
-H "Content-type: application/json" \
-H "Accept: application/json" \
"http://127.0.0.1:5000/api/v1/time-series-datum/xxx"
Graceful Shutdown
Update the main.go file with the following content to support graceful shutdowns of our webserver.
|
|
Final Thoughts - What’s next?
That’s it! We have ourselves a basic API web-app. We have dipped our feat on the shores of the ocean of Golang web-development and the water feels welcoming. Moving forward we’ll need to cover more topics such as:
- How do we read the body of a request? How do we write responses?
- How do we handle using a database?
- How do we handle login and authenticated API endpoints?
- How do we handle sessions?
- How do we handle background processes?
Now onward to the next part of our series: Part 2: Simple Database ».