A Go CRUD API framework so simple a baby could use it.
babyapi
is a super simple framework that automatically creates an HTTP API for create, read, update, and delete operations on a struct. Simply extend the babyapi.DefaultResource
type to get started.
Implement custom request/response handling by implemented Renderer
and Binder
from go-chi/render
. Use provided extension functions to add additional API functionality:
OnCreateOrUpdate
: additional handling for create/update requestsStorage
: set a different storage backend implementing thebabyapi.Storage
interfaceAddCustomRoute
: add more routes on the base APIPatch
: add custom logic for handlingPATCH
requests- And many more! (see examples and docs)
- Override any of the default handlers and use
babyapi.Handler
shortcut to easily render errors and responses
You can also opt to just use the api.Router()
function to get the API's router/handler and add to your application's existing server.
-
Create a new Go module:
mkdir babyapi-example cd babyapi-example go mod init babyapi-example
-
Write
main.go
to create aTODO
struct and initializebabyapi.API
:package main import "github.com/calvinmclean/babyapi" type TODO struct { babyapi.DefaultResource Title string Description string Completed bool } func main() { api := babyapi.NewAPI( "TODOs", "/todos", func() *TODO { return &TODO{} }, ) api.RunCLI() }
-
Run!
go mod tidy go run main.go serve
-
Use the built-in CLI to interact with the API:
# Create a new TODO go run main.go client todos post --data '{"title": "use babyapi!"}' # Get all TODOs go run main.go client todos list # Get TODO by ID (use ID from previous responses) go run main.go client todos get cljvfslo4020kglbctog
In addition to providing the HTTP API backend, babyapi
is also able to create a client that provides access to the base 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 methods for interacting with the base API and MakeRequest
and MakeRequestWithResponse
to interact with custom routes. You can replace the underlying http.Client
and set a request editor function that can be used to set authorization headers for a client.
The babytest
package provides some shortcuts and utilities for easily building table tests or simple individual tests. This allows seamlessly creating tests for an API using the convenient babytest.RequestTest
struct, a function returning an *http.Request
, or a slice of command-line arguments.
Check out some of the examples for examples of using the babytest
package.
If your application uses api.RunCLI()
, you can execute the generate-test
command to generate a boilerplate CRUD test for the API:
go run main.go generate-test
You can bring any storage backend to babyapi
by implementing the Storage
interface. By default, the API will use the built-in KVStorage
with the default configuration for in-memory map.
This storage implementation leverages tarmac-project/hord
to support a variety of key-value store backends. Currently, the babyapi/storage/kv
package provides helpers to create file or redis-based storage implementations.
db, err := kv.NewFileDB(hashmap.Config{
Filename: "storage.json",
})
db, err := kv.NewRedisDB(redis.Config{
Server: "localhost:6379",
})
api.SetStorage(babyapi.NewKVStorage[*TODO](db, "TODO"))
The babyapi.EndDateable
interface can be implemented to enable soft-delete with the KVStorage
. This will set an end-date instead of permanently deleting a resource. Then, deleting it again will permanently delete. Also, the GetAll
implementation will filter out end-dated resources unless the end_dated
query parameter is set to enable getting end-dated resources.
babyapi
provides an Extension
interface that can be applied to any API with api.ApplyExtension()
. Implementations of this interface create custom configurations and modifications that can be applied to multiple APIs. A few extensions are provided by the babyapi/extensions
package:
HATEOAS
: "Hypertext as the engine of application state" is the 3rd and final level of REST API maturity, making your API fully RESTfulKVStorage
: provide a few simple configurations to use theKVStorage
client with a local file or RedisHTMX
: HTMX expects 200 responses from DELETE requests, so this changes the response code
Like anything in software engineering, there are cases where babyapi
is a good choice and others where it's not a great fit.
You should use babyapi
if you:
- Need a resource-driven REST HTTP API and want to get moving quickly. This can be a standalone application or a component of a larger application
- Have multiple APIs that will interact and can benefit from a compatible client with no extra work
- Want to learn about framework development and contribute to an open source project
- Know the extent of the application's scope and know it won't grow beyond
babyapi
's capabilities
You should not use babyapi
if you:
- Need to have ultimate control over the application's execution that might not be compatible with
babyapi
(although maybe you can add support!) - Aren't willing to dig into the framework's code and learn how it works
- Don't understand how to build an API without it. It's important to understand the fundamentals before taking shortcuts
If babyapi
is not a great fit for your use-case, you can still use some of its features to speed up development! Check out the Use As Library example.
Description | Features | |
---|---|---|
TODO list | This example expands upon the base example to create a realistic TODO list application |
|
Nested resources | Demonstrates how to build APIs with nested/related resources. The root resource is an Artist which can have Albums and MusicVideos . Then, Albums can have Songs |
|
Storage | The example shows how to use the babyapi/storage package to implement persistent storage |
|
TODO list with HTMX UI | This is a more complex example that demonstrates an application with HTMX frontend. It uses server-sent events to automatically update with newly-created items |
|
Event RSVP | This is a more complex nested example that implements basic authentication, middlewares, and relationships between nested types. The app can be used to create Events and provide guests with a link to view details and RSVP |
|
Multiple APIs | This example shows how multiple top-level (or any level) sibling APIs can be served, and have CLI functionality, under one root API |
|
Background Worker | This example shows how you can use babyapi in an application alongside background workers and have runtime control over all goroutines |
|
SQL | This example shows how you can build an API with a custom implementation of babyapi.Storage using sqlc |
|
Pokemon Client | This example shows how you can leverage the client and CLI features of babyapi to create a client for an external API |
|
Use As Library | This example shows how a subset of babyapi features can be used as a library rather than a full framework |
|
Also see a full example of an application implementing a REST API using babyapi
in my automated-garden
project.
Please open issues for bugs or feature requests and feel free to create a PR.