Skip to main content

What You’ll Build

This guide walks you through building your first API with Contract. By the end, you’ll have a working todo list API accessible via REST and JSON-RPC. You’ll learn the three-step pattern that every Contract service follows:
  1. Define your interface - The contract that describes your API
  2. Implement the interface - Your business logic
  3. Register and serve - Make it available via HTTP
We’ll also show you the recommended project structure using Go packages, where your service lives in a todo package with types named API and Service (the package name provides the β€œtodo” context).

Prerequisites

Before starting, make sure you have:
  • Go 1.22 or later installed (download Go)
  • A terminal (Command Prompt, Terminal, or any shell)
  • A text editor (VS Code, GoLand, or your favorite)
  • curl for testing (usually pre-installed on Mac/Linux)
If you’re not sure which Go version you have, run:

Step 1: Create Your Project

Create a new directory and initialize a Go module. A Go module is a collection of Go packages with a go.mod file that tracks dependencies:
What this does: Creates a new Go project. The go.mod file tells Go where to find dependencies and what version of Go you’re using. Now create the directory structure for your todo package:
Your project structure will look like this:

Step 2: Install Dependencies

Add the required packages. These commands download the mizu web framework and Contract v2:
What this does:
  • github.com/go-mizu/mizu is the mizu web framework that handles HTTP routing
  • github.com/go-mizu/mizu/contract/v2 is Contract v2, which provides the interface-first API pattern
After running these commands, your go.mod file will list these as dependencies.

Step 3: Create Your Types

Create todo/types.go. This file defines the data structures your API works with. These are called β€œDTOs” (Data Transfer Objects) because they define what data moves between clients and your server:
Why separate input/output types? You might wonder why we have separate types like CreateInput and GetInput instead of just using Todo everywhere. There are good reasons:
  1. Input types describe what clients send to you. For Create, we only need a title - we generate the ID.
  2. Output types describe what you send back. We return the full Todo with the generated ID.
  3. Flexibility: You can add fields to outputs without requiring clients to send them, and vice versa.
  4. Validation: Input types only contain the fields you actually accept.

Step 4: Define Your Interface (The Contract)

Create todo/api.go. This is the heart of Contract - your interface defines what your API can do. Think of it as a β€œmenu” that lists all available operations:
Key points about the interface:
  1. Package naming: The interface is named API (not TodoAPI) because todo.API reads naturally when imported.
  2. Context first: Every method starts with ctx context.Context. This is a Go pattern for passing request-scoped data like timeouts and cancellation signals.
  3. Pointer inputs: Input types are pointers (*CreateInput) so they can be nil for methods that don’t need input.
  4. Error handling: Every method returns error as the last return value. This is how you communicate failures to clients.
  5. Method naming: Names like Create, List, Get, Delete automatically map to HTTP verbs when using REST.

Step 5: Implement Your Interface

Create todo/service.go. This is where your actual code lives - the β€œkitchen” that prepares the dishes from your β€œmenu” (the interface):
Important concepts:
  1. Package naming: The struct is named Service (not TodoService) because todo.Service reads naturally.
  2. Constructor function: NewService() is a common Go pattern for creating initialized instances.
  3. Thread safety: We use sync.RWMutex because HTTP servers handle multiple requests concurrently.
  4. Error handling: Return nil, error to indicate failure. Contract translates this to the appropriate protocol response.

Step 6: Wire Everything Together

Create main.go in your project root. This file imports your todo package and wires everything together:

Complete Project Structure

Your project should now look like this:
Why organize code this way?
  1. Clear separation: Each file has a single responsibility
  2. Package-based naming: todo.API and todo.Service are clearer than TodoAPI and todoService
  3. Testability: You can easily mock todo.API for testing
  4. Scalability: Add more services by creating new packages (user/, order/, etc.)

Step 7: Run Your Server

Before running, ensure all dependencies are properly resolved:
Then start your API server:
You should see:
Leave this terminal running and open a new terminal for testing.

Step 8: Test Your API

Let’s test your API using curl. Open a new terminal window (keep the server running in the first one).

Create a Todo (REST)

What this does:
  • POST tells the server we want to create something
  • /todos is the resource path
  • -H "Content-Type: application/json" tells the server we’re sending JSON
  • -d '{"title": "Buy groceries"}' is the JSON body (our CreateInput)
Expected output:
The server generated an ID (todo_1) and set completed to false.

Create Another Todo

Expected output:

List All Todos (REST)

What this does: A simple GET request to /todos calls our List method. Expected output:

Get a Specific Todo (REST)

What this does: GET request to /todos/{id} calls our Get method with id=todo_1. Expected output:

Delete a Todo (REST)

What this does: DELETE request removes the todo with the given ID. Expected output: Empty (HTTP 204 No Content means success with no body) Verify it’s deleted:
Now you should only see one todo.

Step 9: Try JSON-RPC

The same service is also available via JSON-RPC. JSON-RPC uses a different format where you specify the method name in the request body. This is useful for:
  • Batching: Send multiple requests in one HTTP call
  • RPC-style clients: Some languages prefer explicit method names over HTTP verbs

Create via JSON-RPC

What this does:
  • All JSON-RPC requests go to /rpc as POST
  • "jsonrpc": "2.0" identifies this as JSON-RPC (required)
  • "id": 1 helps match requests to responses (you choose the ID)
  • "method": "todos.create" is {resource}.{method}
  • "params" is our CreateInput
Expected output:

List via JSON-RPC

Batch Requests (JSON-RPC Only)

One of JSON-RPC’s killer features is batching. Send multiple requests in one HTTP call:
All three operations execute in one HTTP call! You get an array of responses back, matched by their id values.

Understanding What Happened

Let’s recap what Contract did for you behind the scenes:
  1. Inspected your interface: When you called contract.Register[todo.API](), Contract used Go’s reflection to discover all methods in the todo.API interface and their input/output types.
  2. Generated JSON schemas: Your Go structs (Todo, CreateInput, etc.) were automatically converted to JSON schemas. These schemas are used for documentation and AI tool definitions.
  3. Created REST endpoints: Based on your method names, Contract created HTTP endpoints:
    • Create β†’ POST /todos (HTTP convention: POST creates resources)
    • List β†’ GET /todos (HTTP convention: GET retrieves resources)
    • Get β†’ GET /todos/ (path parameter for specific resource)
    • Delete β†’ DELETE /todos/ (HTTP convention: DELETE removes resources)
  4. Created JSON-RPC handlers: All methods became available at /rpc:
    • todos.create, todos.list, todos.get, todos.delete
  5. Compile-time safety: If your Service didn’t implement all methods in API, Go’s compiler would have caught it before you even ran the program.

Common Questions

Why doesn’t my method appear as an endpoint?

Methods must follow Contract’s rules:
  • Must be in the interface: Methods only on the struct (not in the interface) won’t be exposed
  • First parameter must be context.Context: This is required for proper request handling
  • Input must be pointer to struct: Use *CreateInput, not CreateInput
  • Last return must be error: Every method needs to report success/failure

How do I add more transports?

Import the transport package and mount it:

How do I handle errors properly?

The quick start uses simple errors.New(). For production, use Contract’s typed errors for proper HTTP status codes:
See the Error Handling guide for all error types.

How do I add a database?

Replace the in-memory map with your database client. The Service struct holds your dependencies:

What’s Next?

Now that you have a working API, explore these topics: