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/usersPOST /api/users
Create a new user
curl -X POST http://localhost:8080/api/users -H "e;Content-Type: application/json"e; -d 'GET /api/users/:id
Get a specific user
curl http://localhost:8080/api/users/1PUT /api/users/:id
Update a user
curl -X PUT http://localhost:8080/api/users/1 -H "e;Content-Type: application/json"e; -d ' "e;data_here"e;DELETE /api/users/:id
Delete a user
curl -X DELETE http://localhost:8080/api/users/1Features 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
- Add database integration (PostgreSQL, MySQL, etc.)
- Implement authentication with JWT tokens
- Add input validation with a validation library
- Explore the middleware example
- Learn about advanced error handling