Why Go?
When we started thinking about building an API in Go, our main motivation was execution speed. We had heard a lot about Go being a fast, efficient, and lightweight language, so we wanted to see for ourselves how it performs when building a simple API.
Go (or Golang) is known for its simplicity, concurrency model, and performance. Unlike many other languages that rely on frameworks with heavy abstractions, Go allows you to build an API with just its standard library or minimal dependencies. This makes it an excellent choice for developers who want control, efficiency, and a clean, maintainable codebase.
So why would someone choose Go for API development? The language compiles to a single binary, making deployment incredibly easy. Its built-in concurrency support ensures APIs handle multiple requests efficiently, and thanks to its static typing and garbage collection, Go strikes a great balance between safety and speed.
In this blog, we’ll walk through building a simple CRUD API using pure Golang, without a database—just a JSON file for persistence. We’ll also integrate hot-reloading with AIR and use the Chi router to handle requests. Let’s dive in!
Project setup
Before diving into the code, let’s first establish a clean and organized folder structure. Keeping the project well-structured makes it easier to maintain and scale. Here’s how we’ve organized our Go API:
data
items.json # Stores our data in JSON format
handlers
listItems.go # Handles fetching all items
... # Other handlers for CRUD operations
model
item.go # Defines the item struct
routes
routes.go # Defines API routes and maps them to handlers
services
response.go # Utility for sending structured API responses
go.mod # Go module file
go.sum # Dependency checksums
main.go # Application entry point
This structure separates concerns effectively:
-
handlers/ contains the logic for handling API requests.
-
model/ defines data structures used across the API.
-
routes/ centralizes route definitions for better readability.
-
services/ abstracts reusable utilities.
-
data/ stores the JSON file acting as our database.
With this setup, we ensure that our Go API remains modular, clean, and scalable. Now, let's move on to building the API step by step!
Let’s implement our first endpoint - GET All items
Now that we have our folder structure set up, let’s implement our first API endpoint: fetching all items from items.json. This will involve the following files:
-
main.go – The entry point of our application.
-
model/item.go – Defines the Item struct.
-
routes/routes.go – Sets up the API routes.
-
handlers/listItems.go – Implements the logic to fetch all items.
1. Define item model
This struct will be used when encoding/decoding JSON data. The struct tags (json:"...") ensure proper mapping between JSON keys and struct fields.
2. Implementing GET handler
The handler function will read data from items.json and return it as a JSON response.
3. Setting up Routes
4. The Entry Point
It’s important to note that we use several service classes to keep our code clean and modular. For example, we use a response service to ensure that every API response—whether a success or an error—is consistently formatted as JSON (services.JsonResponse(w, errorResponse.Status, errorResponse, r)). This approach improves readability, reduces redundancy, and makes it easier to handle responses across multiple endpoints.
Suppose you want to use a custom service for JSON responses instead of the standard http.ResponseWriter, you can do so by adding a response.go file inside the services folder with the following code:
With this, you can replace all direct w.Write(response) calls in your handlers with services.JsonResponse(...), ensuring every response follows a consistent JSON structure.
Now, all that’s left is to start our API by running the following command in the terminal:
Once the server is up and running, we can test our first API endpoint using curl:
This request should return all items stored in items.json in JSON format. If everything works correctly, we have successfully set up our first Go API endpoint!
Let’s add Docker
Great! We now have a working Go API, but there’s one problem—every developer who wants to run the project needs to install Go locally and ensure they have the correct version. This can lead to inconsistencies, especially when working in a team. We don’t want to force every developer to manually install Go and manage versions.
To solve this, we’ll dockerize our API, allowing it to run in a containerized environment with all dependencies included. This ensures that everyone on the team runs the API in the same, controlled environment—no need to install Go manually. In the next step, we’ll set up a Dockerfile and configure our API to run inside a Docker container.
Before we add Docker, let’s address something that has probably been bothering you—every time we make a change, we have to manually restart the API by running go run ./main.go again. This lack of hot reload can slow down development and become frustrating over time.
Since we want to be as efficient as possible and avoid wasting time constantly stopping and restarting the application, we’ll include AIR, a package that enables hot reload for Go applications, directly in our Docker setup. This way, whenever we modify our code, the API will automatically recompile, making development much smoother. Let’s dive into setting up Docker with AIR!
1. Add air to our app
Add necessary files to enable air package
-
.air.config - default setup can be found https://github.com/air-verse/air/blob/master/air_example.toml
-
.air.toml - leave it empty
2. Add Docker setup
And, of course, we need docker-compose.yaml
Great! Now, if you run the following commands:
Your Go application will start inside a Docker container with hot reload enabled, thanks to AIR. This means that every time you make a change in your code, the API will automatically restart without needing to manually stop and restart the container.
To verify that everything is working correctly, try making some changes in your project—for example, modifying a handler or adding a new log statement. If the changes take effect immediately without requiring a manual restart, then our setup is successfully running with Docker and AIR.
Is it really that fast?
Now that we have built our basic API with a GET endpoint to fetch a list of items, we can test just how fast Go is. One of the key advantages of Go is its performance, thanks to its compiled nature and efficient concurrency handling.
To see this in action, we can check our Docker container logs, where we can log the execution time for each request. By making a request to our API and inspecting the logs, we can get a clear picture of how quickly Go processes and returns the data.

As you can see, the response time is incredibly low - 1.25ms, demonstrating how well Go handles API requests, even with a lightweight setup like ours. This speed advantage makes Go an excellent choice for building high-performance backend services.
Conclusion
We have successfully built a simple CRUD API using pure Go, structured it in a clean and modular way, and then dockerized it to ensure consistency across different environments. Additionally, we integrated AIR for hot reload, making our development workflow more efficient by automatically restarting the server whenever we make changes.
Using Go for API development has several advantages:
-
Performance – Go is fast and efficient, making it ideal for high-performance APIs.
-
Simplicity – With Go’s minimalistic syntax and built-in concurrency, writing scalable applications is straightforward.
-
Portability – Thanks to Docker, our API can run anywhere without worrying about dependencies or environment differences.
-
Development speed – With AIR’s hot reload, we save time by avoiding manual restarts after every change.
However, there are also some trade-offs to consider:
-
Manual JSON handling – Unlike some frameworks, Go requires manual JSON parsing and struct handling.
-
Less Built-in convenience – Compared to frameworks like Express.js (Node.js) or Django (Python), Go’s standard library is minimal, requiring more boilerplate code.
-
Learning curve – If you’re new to Go, concepts like strict typing and Goroutines might take some time to master.
Despite these trade-offs, Go remains an excellent choice for building APIs, especially when performance, scalability, and efficiency are key factors. By combining Go with Docker and AIR, we’ve created a streamlined development process that ensures reliability and ease of use for both developers and production environments.