DEV Community

Cover image for The Easiest Way to Create a REST API With Go
Calvin McLean
Calvin McLean

Posted on

The Easiest Way to Create a REST API With Go

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

Next, all you need to do is run the server and use the CLI to interact with it:

go run main.go serve
Enter fullscreen mode Exit fullscreen mode

Simple Example

There are too many features to cover in this article, so the best ways to learn more are:

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

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

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 or AddCustomIDRoute)
  • 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!

Top comments (6)

Collapse
 
soulsbane profile image
Paul Crane

This is really cool. Thanks for the post!

Collapse
 
calvinmclean profile image
Calvin McLean

Thanks! I’m glad you found it interesting

Collapse
 
ashsajal profile image
Ashfiquzzaman Sajal

Sounds like Ruby on rails. Go ahead!

Collapse
 
calvinmclean profile image
Calvin McLean

Wait until you see my next article demonstrating HTML templates!

Collapse
 
blinkinglight profile image
M

had similar mind month ago, nice to see that package already happened!

also have question: can it work with go-gin or other http packages?

Collapse
 
calvinmclean profile image
Calvin McLean • Edited

Unfortunately the current implementation is coupled to chi because it’s what I am more familiar with and I relied on the chi/render package for request/response encoding.

It would probably be better if it change it to use vanilla net/http so I’ll look more into that before a 1.0 release.