A Go CRUD API framework so simple a baby could use it. Now with MCP!
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.StorageinterfaceAddCustomRoute: add more routes on the base APIPatch: add custom logic for handlingPATCHrequests- And many more! (see examples and docs)
- Override any of the default handlers and use
babyapi.Handlershortcut 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.goto create aTODOstruct 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.Search(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-testYou 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 Search implementation will filter out end-dated resources unless the end_dated query parameter is set to enable getting end-dated resources.
Babyapi uses mark3labs/mcp-go to implement a simple MCP Server for CRUD operations. This is also completely customizable with additional tools and options using the provided MCP functions.
MCP can simply be enabled like this:
api := babyapi.NewAPI("TODOs", "/todos", func() *TODO { return &TODO{} })
// Read only
api.EnableMCP(babyapi.MCPPermRead)
// Read and Create only
api.EnableMCP(babyapi.MCPPermRead | babyapi.MCPPermCreate)
// Enable all CRUD operations
api.EnableMCP(babyapi.MCPPermCRUD)
// Custom tools
api.AddMCPTools(...)
// Custom options
api.AddMCPServerOptions(
server.WithInstructions("This is a web server for managing TODO list items"),
...,
)
// Custom HTTP options
api.AddMCPHTTPOptions(...)Check out the simple TODO example and Nested API example!
I recommend testing with mark3labs/mcphost with a config like this:
mcpServers:
todos:
type: remote
url: "${env://URL:-http://localhost:8080/mcp}"
environment:
DEBUG: "${env://DEBUG:-false}"
LOG_LEVEL: "${env://LOG_LEVEL:-info}"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 theKVStorageclient 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.
