Skip to content

REST API Example

A complete CRUD API with validation, error handling, and proper HTTP semantics.

Full Example

package main

import (
    "log"
    "sync"
    "time"

    "github.com/gomarten/marten"
    "github.com/gomarten/marten/middleware"
)

type User struct {
    ID        string    `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
}

var (
    users   = make(map[string]User)
    usersMu sync.RWMutex
    nextID  = 1
)

func main() {
    app := marten.New()

    // Global middleware
    app.Use(
        middleware.RequestID,
        middleware.Logger,
        middleware.Recover,
        middleware.CORS(middleware.DefaultCORSConfig()),
    )

    // Health check
    app.GET("/health", func(c *marten.Ctx) error {
        return c.OK(marten.M{"status": "healthy"})
    })

    // API routes
    api := app.Group("/api/v1")
    api.GET("/users", listUsers)
    api.GET("/users/:id", getUser)
    api.POST("/users", createUser)
    api.PUT("/users/:id", updateUser)
    api.DELETE("/users/:id", deleteUser)

    log.Println("API running on http://localhost:3000")
    app.RunGraceful(":3000", 10*time.Second)
}

Handlers

List Users

func listUsers(c *marten.Ctx) error {
    usersMu.RLock()
    defer usersMu.RUnlock()

    list := make([]User, 0, len(users))
    for _, u := range users {
        list = append(list, u)
    }

    return c.OK(marten.M{
        "users": list,
        "total": len(list),
    })
}

Get User

func getUser(c *marten.Ctx) error {
    id := c.Param("id")

    usersMu.RLock()
    user, exists := users[id]
    usersMu.RUnlock()

    if !exists {
        return c.NotFound("user not found")
    }

    return c.OK(user)
}

Create User with Validation

func createUser(c *marten.Ctx) error {
    var input struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }

    if err := c.BindValid(&input, func() error {
        if input.Name == "" {
            return &marten.BindError{Message: "name is required"}
        }
        if input.Email == "" {
            return &marten.BindError{Message: "email is required"}
        }
        return nil
    }); err != nil {
        return c.BadRequest(err.Error())
    }

    usersMu.Lock()
    id := fmt.Sprintf("%d", nextID)
    nextID++
    user := User{
        ID:        id,
        Name:      input.Name,
        Email:     input.Email,
        CreatedAt: time.Now(),
    }
    users[id] = user
    usersMu.Unlock()

    return c.Created(user)
}

Update User

func updateUser(c *marten.Ctx) error {
    id := c.Param("id")

    usersMu.RLock()
    user, exists := users[id]
    usersMu.RUnlock()

    if !exists {
        return c.NotFound("user not found")
    }

    var input struct {
        Name  string `json:"name"`
        Email string `json:"email"`
    }

    if err := c.Bind(&input); err != nil {
        return c.BadRequest(err.Error())
    }

    if input.Name != "" {
        user.Name = input.Name
    }
    if input.Email != "" {
        user.Email = input.Email
    }

    usersMu.Lock()
    users[id] = user
    usersMu.Unlock()

    return c.OK(user)
}

Delete User

func deleteUser(c *marten.Ctx) error {
    id := c.Param("id")

    usersMu.Lock()
    _, exists := users[id]
    if exists {
        delete(users, id)
    }
    usersMu.Unlock()

    if !exists {
        return c.NotFound("user not found")
    }

    return c.NoContent()
}

Testing the API

# Create user
curl -X POST http://localhost:3000/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"name":"John","email":"john@example.com"}'

# List users
curl http://localhost:3000/api/v1/users

# Get user
curl http://localhost:3000/api/v1/users/1

# Update user
curl -X PUT http://localhost:3000/api/v1/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"John Doe"}'

# Delete user
curl -X DELETE http://localhost:3000/api/v1/users/1

Key Patterns

  • Use c.OK(), c.Created(), c.NoContent() for success responses
  • Use c.BadRequest(), c.NotFound() for error responses
  • Use c.BindValid() for validation
  • Use c.Param() for path parameters
  • Use route groups for API versioning