The Easiest Way to Create a REST API With Go
Calvin McLean
Posted on December 3, 2023
Introduction
Go is known for being easy to learn and providing the fastest path to production. With HTTP functionality built-in to the standard library, you have everything you need without any external dependencies. The lower-level access to HTTP can be a breath of fresh air if you are accustomed to bloated frameworks, but it results in a lot of boilerplate code which eventually becomes tiring to write over and over. This is where babyapi
comes in. I created this library to enable super simple creation of REST APIs without forcing a specific structure on your whole application. It's so easy that a baby could do it!
Baby API
All you need to get started with babyapi
is a struct that implements the babyapi.Resource
interface. This is easily achieved by extending the babyapi.DefaultResource
struct. After completing this simple step, you will have an HTTP API capable of create, read, update, and delete operations. Additionally, babyapi
provides an HTTP client to interact with the API, a CLI to easily use this client, and some shortcuts for unit testing.
babyapi
can also take you beyond the basics. It provides plenty of options for extending the API behavior, adding validation, and even custom API routes. Any storage backend can be integrated by implementing the simple Storage
interface.
This article just scratches the surface of what babyapi
is capable of. If you are interested in learning more, keep an eye out for my future articles and star the GitHub repository.
Getting Started
The goal of babyapi
is to be so easy that a baby could do it. As previously mentioned, babyapi.DefaultResource
already implements the required interface, so it can be used as a starting point for simple resource types. Besides simply implementing the interface, this default struct implements some validations around the ID and uses rs/xid
to create a unique identifer on new resources.
Here is a simple example that extends the default to create an API for TODO items:
package main
import "github.com/calvinmclean/babyapi"
type TODO struct {
babyapi.DefaultResource
Title string
Description string
Completed bool
}
func main() {
api := babyapi.NewAPI[*TODO](
"TODOs", "/todos",
func() *TODO { return &TODO{} },
)
api.RunCLI()
}
Next, all you need to do is run the server and use the CLI to interact with it:
go run main.go serve
There are too many features to cover in this article, so the best ways to learn more are:
- Getting Started tutorial
- Additional examples
pkg.go.dev
documentation- My future articles :)
Client
The struct used to create the server also enables a built-in client for interacting with the babyapi
server. The client can also be used in the application or other Go applications interacting with your API. It supports top-level and nested API resources and allows easy access to all CRUD endpoints.
// Create a client from an existing API struct (mostly useful for unit testing):
client := api.Client(serverURL)
// Create a client from the Resource type:
client := babyapi.NewClient[*TODO](addr, "/todos")
// Create a new TODO item
todo, err := client.Post(context.Background(), &TODO{Title: "use babyapi!"})
// Get an existing TODO item by ID
todo, err := client.Get(context.Background(), todo.GetID())
// Get all incomplete TODO items
incompleteTODOs, err := client.GetAll(context.Background(), url.Values{
"completed": []string{"false"},
})
// Delete a TODO item
err := client.Delete(context.Background(), todo.GetID())
The client provides more general methods for interacting with server: MakeRequest
and MakeRequestWithResponse
. The client is also cutomizable. You can replace the underlying http.Client
to use a custom RoundTripper
or use SetRequestEditor
to add a pre-request function that modifies the http.Request
.
Storage
You can bring any storage backend to babyapi
by implementing the Storage
interface. By default, the API will use the built-in MapStorage
which just uses an in-memory map.
In an effort to provide actual persistent storage out of the box, the babyapi/storage
package uses madflojo/hord
to support a variety of key-value store backends. Additionally, babyapi/storage
provides helper functions for initializing the hord
client for Redis or file-based storage.
db, err := storage.NewFileDB(hashmap.Config{
Filename: "storage.json",
})
db, err := storage.NewRedisDB(redis.Config{
Server: "localhost:6379",
})
api.SetStorage(storage.NewClient[*TODO](db, "TODO"))
Conclusion & Roadmap
babyapi
is a very new project and still has a lot of potential for new features and improvements. However, my goal is to keep it as simple and barebones as possible, so I plan to focus on usability and testing improvements. I will add new features if use-cases or feature requests come up. These are some of the things coming next:
- Improve CLI to have better usage instructions and implement flags when multiple IDs are required for nested APIs
- Improve default logging and implement log-levels
- Allow CLI to access non-CRUD endpoints (endpoints added by
AddCustomRoute
orAddCustomIDRoute
) - Create more automated testing. Ideally, I will create a generator that reads your source file using
babyapi
and generates a test structure with some default tests and room to extend them
Also keep an eye out for more articles that dig deeper into some of the core features of babyapi
!
Posted on December 3, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.