zoobzio December 15, 2025 Edit this page

Quickstart

Get a type-safe HTTP API running in 5 minutes.

Installation

go get github.com/zoobz-io/rocco

Your First API

Create a file main.go:

package main

import (
    "fmt"
    "github.com/zoobz-io/rocco"
)

// 1. Define your input type (validate tags generate OpenAPI constraints)
type CreateUserInput struct {
    Name  string `json:"name" validate:"required,min=2,max=100"`
    Email string `json:"email" validate:"required,email"`
}

// 2. Define your output type
type UserOutput struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    // 3. Create an engine
    engine := rocco.NewEngine()

    // 4. Create a typed handler
    createUser := rocco.POST[CreateUserInput, UserOutput]("/users",
        func(req *rocco.Request[CreateUserInput]) (UserOutput, error) {
            // req.Body is your parsed CreateUserInput
            return UserOutput{
                ID:    "usr_" + req.Body.Name[:3],
                Name:  req.Body.Name,
                Email: req.Body.Email,
            }, nil
        },
    ).WithSuccessStatus(201) // Return 201 Created

    // 5. Register handler
    engine.WithHandlers(createUser)

    // 6. Start server
    fmt.Println("Server running at http://localhost:8080")
    engine.Start(rocco.HostAll, 8080)
}

Run it:

go run main.go

Test Your API

Create a user:

curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name": "John Doe", "email": "john@example.com"}'

Response:

{
  "id": "usr_Joh",
  "name": "John Doe",
  "email": "john@example.com"
}

Try invalid JSON:

curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{invalid}'

Response (422 Unprocessable Entity):

{
  "code": "UNPROCESSABLE_ENTITY",
  "message": "invalid request body"
}

Note: Runtime validation is opt-in. To validate inputs, implement the Validatable interface on your types. See Concepts: Validation for details.

View OpenAPI Documentation

Rocco automatically generates OpenAPI documentation. Visit:

  • http://localhost:8080/openapi - OpenAPI JSON spec
  • http://localhost:8080/docs - Interactive Scalar documentation

Add More Handlers

Expand your API with GET, PUT, DELETE handlers:

// GET /users/{id}
getUser := rocco.GET[rocco.NoBody, UserOutput]("/users/{id}",
    func(req *rocco.Request[rocco.NoBody]) (UserOutput, error) {
        userID := req.Params.Path["id"]
        // Fetch user from database...
        return UserOutput{ID: userID, Name: "John", Email: "john@example.com"}, nil
    },
).WithPathParams("id")

// GET /users (with query params)
type UserListOutput struct {
    Users []UserOutput `json:"users"`
    Total int          `json:"total"`
}

listUsers := rocco.GET[rocco.NoBody, UserListOutput]("/users",
    func(req *rocco.Request[rocco.NoBody]) (UserListOutput, error) {
        page := req.Params.Query["page"]
        limit := req.Params.Query["limit"]
        // Query database...
        return UserListOutput{Users: []UserOutput{}, Total: 0}, nil
    },
).WithQueryParams("page", "limit")

// Register all handlers
engine.WithHandlers(createUser, getUser, listUsers)

Handle Errors

Use sentinel errors for consistent error responses:

getUser := rocco.GET[rocco.NoBody, UserOutput]("/users/{id}",
    func(req *rocco.Request[rocco.NoBody]) (UserOutput, error) {
        user, err := db.FindUser(req.Params.Path["id"])
        if err != nil {
            // Return 404 Not Found
            return UserOutput{}, rocco.ErrNotFound.WithMessage("user not found")
        }
        return UserOutput{...}, nil
    },
).
    WithPathParams("id").
    WithErrors(rocco.ErrNotFound) // Declare possible errors

What's Next?

You've built a type-safe API with automatic OpenAPI documentation. Continue learning:

See Also