Skip to main content

What is Registration?

Registration is the process of telling Contract about your service. Think of it like introducing your service to Contract: “Here’s my interface (what I can do) and my implementation (how I do it). Please make me available to clients.” When you register a service, Contract does several things behind the scenes:
  1. Inspects your interface - Uses Go’s reflection to discover all methods in your interface
  2. Extracts type information - Finds all input and output types for each method
  3. Generates schemas - Creates JSON schemas from your Go types (used for validation and documentation)
  4. Creates invokers - Builds efficient callable wrappers for your methods
  5. Prepares HTTP bindings - Determines HTTP methods and paths for each operation
After registration, you have a *RegisteredService that can be mounted on any transport (REST, JSON-RPC, MCP, etc.).

Basic Registration

The simplest registration takes your implementation and interface:
The generic parameter [todo.API] is crucial - it tells Contract which interface defines your API. Your implementation must satisfy this interface, which Go’s compiler verifies at compile time.

Why the Generic Parameter?

You might wonder why we explicitly specify the interface instead of just passing the implementation. There are good reasons:
This separation between “what’s public” (interface) and “what’s internal” (implementation) is a fundamental benefit of the interface-first approach.

Registration Options

Options let you customize how your service is registered. Pass them as additional arguments after the implementation:
Let’s explore each option in detail.

WithName

Sets the service name. This name appears in documentation and is used for method namespacing:
Default: If not specified, Contract uses the interface name (e.g., “API” from todo.API). Where the name appears:
  • OpenAPI specification: The info.title field
  • JSON-RPC: Method prefix (e.g., Todo.Create or todos.create depending on resource)
  • MCP: Tool group name shown to AI assistants
Example:

WithDescription

Adds a human-readable description to your service. This helps users understand what your API does:
Where the description appears:
  • OpenAPI specification: The info.description field
  • MCP: Server description shown to AI assistants before they use your tools
  • Generated documentation: Any auto-generated API docs
Tip: Write descriptions that help both humans and AI understand your API. Be specific about what operations are available and what the service is for.

WithDefaultResource

Groups all methods under a resource name. This is one of the most important options for REST APIs:
What it does: Without a resource, your methods would be at the root path (rarely what you want):
With WithDefaultResource("todos"), methods get proper RESTful paths:
It also affects JSON-RPC method names:
Why “todos” (plural)?: REST convention uses plural nouns for collections. /todos represents the collection of all todos, /todos/{id} represents a single todo.

WithResource

Groups specific methods under a resource name. Use this when one interface manages multiple resources:
Tip: For cleaner organization, consider using separate packages and interfaces for each resource:

WithMethodHTTP

Override the HTTP binding for a specific method. Use this when automatic inference doesn’t match your needs:
Common use cases:
  1. API versioning: Add version prefix to paths
  2. Custom actions: Operations that don’t fit CRUD
  3. Nested resources: Related sub-resources

WithHTTP

Set HTTP bindings for multiple methods at once. Useful when you need to customize many methods:
This is equivalent to calling WithMethodHTTP for each method, but more concise when customizing multiple bindings.

WithDefaults

Set global defaults for the service that appear in generated specifications:
Where defaults are used:
  • OpenAPI specification: servers array includes the BaseURL
  • Client generators: Generated clients use these as defaults
  • Documentation: Shows users the production URL

WithStreaming

Mark a method as supporting streaming responses:
Available streaming modes:

The Registered Service

After registration, you get a *RegisteredService. This object provides several useful methods:

Descriptor

Get the service descriptor containing all metadata about your service:
Use cases for Descriptor:
  • Generating custom documentation
  • Building admin dashboards that show available endpoints
  • Debugging registration issues

Call

Invoke a method programmatically without going through HTTP:
Parameters:
  • ctx - Context for the call (timeouts, cancellation)
  • resource - Resource name as string (e.g., “todos”)
  • method - Method name in lowercase (e.g., “create”, “list”)
  • input - Input value, or nil for methods without input
Use cases for Call:
  • Testing without HTTP
  • Internal service-to-service calls
  • Building CLI tools that use the same service logic

NewInput

Create a new instance of a method’s input type. This is useful for transports and testing:
Why is this useful?: Transports need to create input instances before unmarshaling JSON into them. They use NewInput to get the correct type, then unmarshal the request body into it.

How Method Names are Transformed

Contract transforms your Go method names when exposing them as API methods. The transformation is simple: Rule: The first letter is lowercased, the rest stays the same. This follows the JavaScript/JSON convention of using camelCase for property and method names. When clients call your API, they use these lowercase method names.

How HTTP Bindings are Inferred

Contract automatically determines the HTTP method and path based on your Go method name. This inference follows REST conventions.

Method Name to HTTP Verb

Contract looks at how your method name starts to determine the HTTP verb:

Path Generation

The path is determined by the method pattern and resource name:

Complete Example

Path Parameter Extraction

For methods with {id} in the path (Get, Update, Delete), Contract needs to know which field in your input struct contains the ID.

Default Behavior

Contract looks for these fields in your input struct, in order:
  1. Field with path:"id" tag - Explicit path parameter binding
  2. Field named ID - Standard Go naming
  3. Field ending in ID - Like TodoID, UserID

Using the path Tag

The path tag explicitly marks a field as a path parameter:
When a client calls GET /todos/abc123, Contract:
  1. Extracts abc123 from the URL path
  2. Creates a new GetInput instance
  3. Sets the ID field to "abc123"
  4. Passes this to your method

Multiple Path Parameters

For nested resources, you can have multiple path parameters:

Complete Registration Example

Here’s a full example demonstrating various registration options with package-based organization:
Output:

Registration Errors

Registration can fail if your interface doesn’t follow Contract’s rules. Here are common issues:

Missing Context Parameter

Every method must have context.Context as the first parameter:
Why context is required: Context carries request-scoped values like timeouts, cancellation signals, and authentication information. Every API method needs this.

Invalid Return Type

Methods can only return (*Output, error) or just error:

Implementation Doesn’t Match Interface

The implementation must implement all methods in the interface with exact signatures:
Tip: Add a compile-time check in your service file:

Best Practices

Use Descriptive Metadata

Good names and descriptions help users (and AI assistants) understand your API:

Organize with Separate Packages

Prefer separate packages over one giant interface:

Create a Register Function

Keep registration logic close to the implementation:

Common Questions

Can I register multiple services?

Yes! Register each service separately and mount them all:

Can I modify registration after it’s created?

No, registration is immutable. If you need different options, create a new registration:

How do I access the original implementation?

The registered service wraps your implementation. Keep a reference if you need direct access:

What’s Next?

Now that you understand registration:
  • Type System - How Go types are converted to JSON schemas
  • Transports - Mount your service on REST, JSON-RPC, or MCP
  • Error Handling - Handle errors consistently across protocols