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 the babyapi.Storage interfaceAddCustomRoute: add more routes on the base APIPatch: add custom logic for handling PATCH requestsbabyapi.Handler shortcut to easily render errors and responsesYou 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.
shell
mkdir babyapi-example
cd babyapi-example
go mod init babyapi-example
main.go to create a TODO struct and initialize babyapi.API:```go 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() } ```
shell
go mod tidy
go run main.go serve
```shell # 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-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 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 the KVStorage client with a local file or RedisHTMX: HTMX expects 200 responses from DELETE requests, so this changes the response codebabyapiLike 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 |
|
$ claude mcp add babyapi \
-- python -m otcore.mcp_server <graph>