REST API Example

A complete RESTful API with CRUD operations and database integration

Complete REST API

This example demonstrates a full REST API for managing users:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "strconv"
    "sync"
    "time"
    "github.com/user001/squirrel"
)

// User represents a user in our system
type User struct {
    ID        int       `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// In-memory database (use a real database in production)
type UserStore struct {
    users  map[int]*User
    nextID int
    mutex  sync.RWMutex
}

func NewUserStore() *UserStore {
    return &UserStore{
        users:  make(map[int]*User),
        nextID: 1,
    }
}

func (s *UserStore) Create(user *User) *User {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    
    user.ID = s.nextID
    user.CreatedAt = time.Now()
    user.UpdatedAt = time.Now()
    s.users[user.ID] = user
    s.nextID++
    
    return user
}

func (s *UserStore) GetAll() []*User {
    s.mutex.RLock()
    defer s.mutex.RUnlock()
    
    users := make([]*User, 0, len(s.users))
    for _, user := range s.users {
        users = append(users, user)
    }
    return users
}

func (s *UserStore) GetByID(id int) *User {
    s.mutex.RLock()
    defer s.mutex.RUnlock()
    
    return s.users[id]
}

func (s *UserStore) Update(id int, updates *User) *User {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    
    user := s.users[id]
    if user == nil {
        return nil
    }
    
    if updates.Name != "" {
        user.Name = updates.Name
    }
    if updates.Email != "" {
        user.Email = updates.Email
    }
    user.UpdatedAt = time.Now()
    
    return user
}

func (s *UserStore) Delete(id int) bool {
    s.mutex.Lock()
    defer s.mutex.Unlock()
    
    if _, exists := s.users[id]; !exists {
        return false
    }
    
    delete(s.users, id)
    return true
}

// Global user store
var userStore = NewUserStore()

func main() {
    mux := squirrel.NewSqurlMux()
    
    // Middleware
    mux.Use(squirrel.LoggingMiddleware())
    mux.Use(squirrel.RecoveryMiddleware())
    mux.Use(corsMiddleware())
    mux.Use(jsonMiddleware())
    
    // API routes
    mux.HandleFunc("GET /api/users", listUsersHandler)
    mux.HandleFunc("POST /api/users", createUserHandler)
    mux.HandleFunc("GET /api/users/:id", getUserHandler)
    mux.HandleFunc("PUT /api/users/:id", updateUserHandler)
    mux.HandleFunc("DELETE /api/users/:id", deleteUserHandler)
    
    // Health check
    mux.HandleFunc("GET /api/health", healthHandler)
    
    // Seed some initial data
    seedData()
    
    fmt.Println("REST API server starting on http://localhost:8080")
    log.Fatal(squirrel.ListenAndServe(":8080", mux))
}

// Middleware functions
func corsMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            w.Header().Set("Access-Control-Allow-Origin", "*")
            w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
            w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
            
            if r.Method == "OPTIONS" {
                w.WriteStatus(200)
                return
            }
            
            next(w, r)
        }
    }
}

func jsonMiddleware() squirrel.Middleware {
    return func(next squirrel.HandlerFunc) squirrel.HandlerFunc {
        return func(w *squirrel.Response, r *squirrel.Request) {
            w.Header().Set("Content-Type", "application/json")
            next(w, r)
        }
    }
}

// Handler functions
func listUsersHandler(w *squirrel.Response, r *squirrel.Request) {
    users := userStore.GetAll()
    w.JSON(map[string]interface{}{
        "users": users,
        "count": len(users),
    })
}

func createUserHandler(w *squirrel.Response, r *squirrel.Request) {
    var user User
    if err := r.ParseJSON(&user); err != nil {
        w.WriteStatus(400)
        w.JSON(map[string]string{"error": "Invalid JSON"})
        return
    }
    
    // Validation
    if user.Name == "" || user.Email == "" {
        w.WriteStatus(400)
        w.JSON(map[string]string{"error": "Name and email are required"})
        return
    }
    
    createdUser := userStore.Create(&user)
    w.WriteStatus(201)
    w.JSON(createdUser)
}

func getUserHandler(w *squirrel.Response, r *squirrel.Request) {
    idStr := r.PathValue("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        w.WriteStatus(400)
        w.JSON(map[string]string{"error": "Invalid user ID"})
        return
    }
    
    user := userStore.GetByID(id)
    if user == nil {
        w.WriteStatus(404)
        w.JSON(map[string]string{"error": "User not found"})
        return
    }
    
    w.JSON(user)
}

func updateUserHandler(w *squirrel.Response, r *squirrel.Request) {
    idStr := r.PathValue("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        w.WriteStatus(400)
        w.JSON(map[string]string{"error": "Invalid user ID"})
        return
    }
    
    var updates User
    if err := r.ParseJSON(&updates); err != nil {
        w.WriteStatus(400)
        w.JSON(map[string]string{"error": "Invalid JSON"})
        return
    }
    
    user := userStore.Update(id, &updates)
    if user == nil {
        w.WriteStatus(404)
        w.JSON(map[string]string{"error": "User not found"})
        return
    }
    
    w.JSON(user)
}

func deleteUserHandler(w *squirrel.Response, r *squirrel.Request) {
    idStr := r.PathValue("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        w.WriteStatus(400)
        w.JSON(map[string]string{"error": "Invalid user ID"})
        return
    }
    
    if !userStore.Delete(id) {
        w.WriteStatus(404)
        w.JSON(map[string]string{"error": "User not found"})
        return
    }
    
    w.WriteStatus(204)
}

func healthHandler(w *squirrel.Response, r *squirrel.Request) {
    w.JSON(map[string]interface{}{
        "status": "healthy",
        "timestamp": time.Now(),
        "users_count": len(userStore.GetAll()),
    })
}

func seedData() {
    userStore.Create(&User{Name: "John Doe", Email: "john@example.com"})
    userStore.Create(&User{Name: "Jane Smith", Email: "jane@example.com"})
    userStore.Create(&User{Name: "Bob Johnson", Email: "bob@example.com"})
}

API Endpoints

GET /api/users

List all users

curl http://localhost:8080/api/users

POST /api/users

Create a new user

curl -X POST http://localhost:8080/api/users -H &quote;Content-Type: application/json&quote; -d '

GET /api/users/:id

Get a specific user

curl http://localhost:8080/api/users/1

PUT /api/users/:id

Update a user

curl -X PUT http://localhost:8080/api/users/1 -H &quote;Content-Type: application/json&quote; -d ' &quote;data_here&quote;

DELETE /api/users/:id

Delete a user

curl -X DELETE http://localhost:8080/api/users/1

Features Demonstrated

CRUD Operations

  • • Create users (POST)
  • • Read users (GET)
  • • Update users (PUT)
  • • Delete users (DELETE)

Data Management

  • • In-memory data store
  • • Thread-safe operations
  • • Data validation
  • • Timestamps

HTTP Features

  • • Proper status codes
  • • JSON responses
  • • CORS support
  • • Error handling

Best Practices

  • • Middleware usage
  • • Input validation
  • • Consistent error responses
  • • Health check endpoint

Production Considerations

⚠️ Important Notes

  • • This example uses in-memory storage - use a real database in production
  • • Add authentication and authorization
  • • Implement rate limiting
  • • Add request validation middleware
  • • Use proper logging and monitoring
  • • Add pagination for list endpoints

Next Steps