Skip to content

calvinmclean/babyapi

Repository files navigation

Baby API

GitHub go.mod Go version (subdirectory of monorepo) GitHub Workflow Status License Go Reference codecov

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 requests
  • Storage: set a different storage backend implementing the babyapi.Storage interface
  • AddCustomRoute: add more routes on the base API
  • Patch: add custom logic for handling PATCH 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

Getting Started

  1. Create a new Go module:

    mkdir babyapi-example
    cd babyapi-example
    go mod init babyapi-example
  2. Write main.go to create a TODO struct and initialize babyapi.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()
    }
  3. Run!

    go mod tidy
    go run main.go serve
  4. 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

Simple Example

Client

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.

Testing

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.

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.

The babyapi/storage/kv package provides another generic Storage implementation using madflojo/hord to support a variety of key-value store backends. babyapi/storage/kv 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"))

Extensions

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 RESTful
  • KVStorage: provide a few simple configurations to use the babyapi/storage package's KV storage client with a local file or Redis
  • HTMX: HTMX expects 200 responses from DELETE requests, so this changes the response code

Examples

Description Features
TODO list This example expands upon the base example to create a realistic TODO list application
  • Custom PATCH logic
  • Additional request validation
  • Automatically set CreatedAt field
  • Query parameter parsing to only show completed items
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
  • Nested API resources
  • Custom ResponseWrapper to add fields from related resources
  • HATEOAS Extension for hypermedia linking
Storage The example shows how to use the babyapi/storage package to implement persistent storage
  • Use SetStorage to use a custom storage implementation
  • Create a hord storage client using babyapi/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
  • Implement babyapi.HTMLer for HTML responses
  • Set custom HTTP response codes per HTTP method
  • Use built-in helpers for handling server-sent events on a custom route
  • Use SetOnCreateOrUpdate to do additional actions on create
  • Handle HTML forms as input instead of JSON (which works automatically and required no changes)
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
  • Demonstrates middlewares and nested resource relationships
  • Authentication
  • Custom non-CRUD endpoints
  • More complex HTML templating
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
  • Use NewRootAPI to create a root API
  • Add multiple children to create siblings
Background Worker This example shows how you can use babyapi in an application alongside background workers and have runtime control over all goroutines
  • Use WithContext to add a context to an API so the API will stop when the context is cancelled
  • Use api.Done() to have other goroutines stop when the API is stopped
SQL This example shows how you can build an API with a custom implementation of babyapi.Storage using sqlc
  • Implement an Extension using a custom implementation of babyapi.Storage
  • Use api.Done() to clean up DB resources
  • Extend the built-in CLI to add flags or other customizations

Also see a full example of an application implementing a REST API using babyapi in my automated-garden project.

Contributing

Please open issues for bugs or feature requests and feel free to create a PR.

About

A Go CRUD API framework so simple a baby could use it.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages