Quick Start (30 Seconds to Running API)
# Clone and setup git clone https://github.com/nicolasbonnici/gorest.git cd examples/basic-api cp .env.dist .env # Start database and generate API docker compose up -d make generate make runYour API is now running at http://localhost:3000 with interactive documentation at http://localhost:3000/openapi.
Start with Your Data Model
Define your business objects and their relationships using standard SQL:
-- schema.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
firstname TEXT NOT NULL,
lastname TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
password TEXT,
created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE todo (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Two tables with a foreign key relationship. That’s our entire starting point in this post.
What Gets Generated Automatically
Complete REST API Endpoints
For Users:
-
POST /users– Create user -
GET /users– List users (with pagination, filtering, ordering) -
GET /users/{id}– Get user -
PUT /users/{id}– Update user -
DELETE /users/{id}– Delete user
For Todos:
-
POST /todos– Create todo -
GET /todos– List todos -
GET /todos/{id}– Get todo -
PUT /todos/{id}– Update todo -
DELETE /todos/{id}– Delete todo
Code Structure
gorest/
├── cmd/
│ └── server/
│ └── main.go # Application entry point
├── internal/
│ ├── models/ # Database models
│ │ ├── user.go
│ │ └── todo.go
│ ├── dto/ # Data Transfer Objects
│ │ ├── user_dto.go
│ │ └── todo_dto.go
│ ├── hooks/ # Your business logic goes here
│ │ ├── user.go
│ │ └── todo.go
│ └── resources/ # REST resource configurations
│ ├── user.go
│ └── todo.go
└── openapi.json # API documentation
Key point: Generated files (models, DTOs, resources) are recreated when you run generate. Your custom business logic goes in hooks/, which are never overwritten.
Interactive API Documentation
Access at http://localhost:3000/openapi:
- Browse all endpoints with request/response schemas
- Test API calls directly from browser
- Copy code examples in multiple languages
- JSON spec available at
/openapi.jsonfor Postman, Insomnia, or client SDK generation
Authentication & Security
Built-in JWT Authentication
Registration and login endpoints are automatically generated:
# Register a new user
curl -X POST http://localhost:3000/register
-H "Content-Type: application/json"
-d '{
"firstname": "John",
"lastname": "Doe",
"email": "john@example.com",
"password": "secure123"
}'
# Login and get JWT token
curl -X POST http://localhost:3000/login
-H "Content-Type: application/json"
-d '{
"email": "john@example.com",
"password": "secure123"
}'
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "abc-123",
"firstname": "John",
"lastname": "Doe",
"email": "john@example.com"
}
}
All protected endpoints automatically require the JWT token:
curl -H "Authorization: Bearer YOUR_TOKEN"
http://localhost:3000/todos
Securing Sensitive Data with DTOs
Control exactly what data is exposed through Data Transfer Objects:
// Database model - contains ALL fields
type Todo struct {
gorest.Model
ID string `json:"id" gorm:"primaryKey"`
Title string `json:"title"`
Content string `json:"content"`
Password string `json:"-"` // Never serialized
UserID string `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
}
// What clients see when reading
type TodoReadDTO struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
UserID string `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
// Password is completely absent - type-safe
}
// What clients can send when creating
type TodoWriteDTO struct {
Title string `json:"title" validate:"required,min=3"`
Content string `json:"content"`
Password string `json:"password" validate:"required,min=8"`
// UserID is absent - auto-populated server-side
}
Benefits:
- Type-safe: Compiler prevents exposing sensitive fields
- Flexible: Different DTOs per operation (list, detail, create, update)
- Self-documenting: API contract is explicit
- Zero overhead: No runtime transformation
Advanced Querying Out of the Box
Filtering
GET /todos?title[like]=meeting # Pattern matching
GET /todos?priority[gte]=5 # Greater than or equal
GET /todos?status[]=active&status[]=pending # Multiple values (OR)
Ordering
GET /todos?order[priority]=desc&order[created_at]=asc
Pagination
GET /todos?page=2&limit=20
Relationship Expansion
# Automatically expand foreign keys
GET /todos?expand=user
# Response includes nested user data
{
"id": "todo-123",
"title": "Buy groceries",
"user": {
"id": "user-456",
"firstname": "John",
"email": "john@example.com"
}
}
Adding Business Logic With Hooks
Hooks execute at key moments in the request lifecycle without modifying generated code.
Available Hooks
-
StateProcessor– Validate/transform data before Create/Update/Delete -
SQLQueryOverride– Replace default queries with custom SQL -
SQLQueryListener– Intercept queries before/after execution -
Authorize– Add authentication/authorization logic
Example 1: Validation and Auto-Population
func (h *TodoHooks) StateProcessor(ctx context.Context, operation hooks.Operation, id any, todo *models.Todo) error {
switch operation {
case hooks.OperationCreate:
// Validate
if todo.Title == "" {
return fmt.Errorf("title is required")
}
if len(todo.Title) < 3 {
return fmt.Errorf("title must be at least 3 characters")
}
// Auto-populate user_id from JWT authentication
// This happens server-side - clients cannot spoof it
if userID := ctx.Value("user_id"); userID != nil {
todo.UserID = userID.(string)
}
}
return nil
}
Result: Users can’t assign todos to each other, and data is validated before reaching the database.
Example 2: Custom Query Logic
func (h *TodoHooks) SQLQueryOverride(ctx context.Context, operation hooks.Operation, id any) (*sqlbuilder.SelectQuery, error) {
if operation == hooks.OperationList {
// Only show the authenticated user's incomplete todos
userID := ctx.Value("user_id")
query := sqlbuilder.
Select("id", "title", "content", "user_id").
From("todo").
Where("completed = FALSE AND user_id = ?", userID)
return query, nil
}
return nil, nil
}
Result: Every GET /todos automatically filters to the authenticated user’s incomplete tasks.
Example 3: Authorization Control
func (h *TodoHooks) Authorize(ctx context.Context, operation hooks.Operation, id any) error {
userID := ctx.Value("user_id")
if userID == nil {
return fmt.Errorf("authentication required")
}
// Custom permission checks
if operation == hooks.OperationDelete {
// Only admins can delete
if !isAdmin(userID) {
return fmt.Errorf("insufficient permissions")
}
}
return nil
}
Why Go and GoREST?
Language and Framework Choice
To build a REST API, you need both a language and framework. We chose Go with GoREST based on these technical advantages:
Performance Benchmarks
- Response times: Sub-millisecond under normal load
- Concurrent requests: Handle thousands simultaneously via goroutines
- Startup time: ~1ms (vs. 1-2 seconds for interpreted languages)
- Compilation: Direct to machine code with no runtime overhead
Development Experience
- Clean syntax and strong typing reduce bugs
- Built-in testing frameworks and profiling tools
- Excellent standard library for HTTP, JSON, and database operations
- Simple concurrency with goroutines and channels
- Code remains maintainable months later
Production Benefits
- Single binary deployment (detailed in next section)
- Cross-platform compilation built-in
- No dependency version conflicts
- Trivial CI/CD pipelines
GoREST Framework Goals
GoREST focuses on eliminating boilerplate while maintaining flexibility:
- Code Generation – Define your schema once, get a complete API
- Consistency – Predictable routes, uniform behavior, automatic validation
- Extensibility – Add business logic via hooks without modifying generated code
- Performance – Leverage Go’s speed and efficiency
- Database Flexibility – Works with PostgreSQL, MySQL, SQLite
Deployment: The Single Binary Advantage
One of Go’s most powerful features is that your entire application compiles into a single, self-contained executable. This fundamentally changes how you deploy and manage applications.
Production Deployment Scenarios
GoREST deployment:
# Build once
make build
# Deploy
scp ./bin/gorest user@server:/opt/gorest/
./gorest
# That's it. No dependencies. No runtime.
1. Bare Metal / VM with systemd
[Unit]
Description=GoREST API Server
After=network.target postgresql.service
[Service]
Type=simple
User=api
WorkingDirectory=/opt/gorest
EnvironmentFile=/opt/gorest/.env
ExecStart=/opt/gorest/bin/gorest
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
sudo systemctl enable gorest
sudo systemctl start gorest
2. Docker (Multi-stage Build)
# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN make build
# Final stage - just the binary
FROM scratch
COPY --from=builder /app/bin/gorest /gorest
EXPOSE 3000
CMD ["https://dev.to/gorest"]
Result: 10-20MB Docker image (vs. 500MB+ for Node.js/Python apps)
3. Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: gorest-api
spec:
replicas: 3
template:
spec:
containers:
- name: gorest
image: your-registry/gorest:latest
ports:
- containerPort: 3000
resources:
requests:
memory: "20Mi"
cpu: "100m"
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
Each pod starts in milliseconds and uses minimal resources.
4. AWS Lambda (Custom Runtime)
# Package as custom runtime
zip function.zip gorest
aws lambda create-function --runtime provided.al2
# Cold start: ~100ms (vs. seconds for interpreted languages)
5. Edge & Serverless Platforms
Deploy to Cloudflare Workers, Fly.io, or any edge platform. Small binary size and instant startup are ideal for serverless environments.
Cross-Platform Compilation
Build for any platform from any platform:
# Build for Linux from your Mac
GOOS=linux GOARCH=amd64 make build
# Build for Windows
GOOS=windows GOARCH=amd64 make build
# Build for ARM (Raspberry Pi, M1 Mac)
GOOS=linux GOARCH=arm64 make build
CI/CD Pipeline Example
# GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: make build
- name: Test
run: ./bin/gorest --version
- name: Deploy
run: scp ./bin/gorest server:/app/
No dependency caching, no runtime installation, no complex build pipelines.
Version Management
# No runtime version conflicts
# Each binary is independent and portable
/opt/api-v1/gorest # Old version
/opt/api-v2/gorest # New version
# Instant rollback
ln -sf /opt/api-v1/gorest /usr/local/bin/gorest
# Instant upgrade
ln -sf /opt/api-v2/gorest /usr/local/bin/gorest
Why This Matters
- Zero Dependencies: No runtime installation, no package managers, no dependency hell
- Consistent Behavior: Same binary runs identically across dev, staging, and production
- Reduced Attack Surface: No runtime vulnerabilities, minimal OS dependencies
- Cost Efficiency: Smaller instances, cheaper hosting, more instances per server
- Faster Deployment: Copy one file, run it
- Simplified Operations: No dependency updates, no runtime patches
Complete Example: From Schema to Production
Let’s walk through a complete workflow:
Step 1: Define Schema
CREATE TABLE todo (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
content TEXT NOT NULL,
user_id UUID REFERENCES users(id),
created_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Step 2: Generate and Build
make generate # Generates models, DTOs, resources, OpenAPI docs
make build # Compiles to single binary in ./bin/gorest
Step 3: Deploy
# Copy to server
scp ./bin/gorest user@server:/opt/api/
# Run
ssh user@server
cd /opt/api
./gorest
Step 4: Use Your API
# Register and login (see Authentication section for details)
TOKEN="your-jwt-token-here"
# Create todo
curl -X POST http://your-server:3000/todos
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"title":"Buy groceries","content":"Milk, eggs, bread"}'
# List todos with filtering and ordering
curl -H "Authorization: Bearer $TOKEN"
"http://your-server:3000/todos?title[like]=groceries&order[created_at]=desc"
# Get specific todo
curl -H "Authorization: Bearer $TOKEN"
"http://your-server:3000/todos/{id}"
# Update todo
curl -X PUT http://your-server:3000/todos/{id}
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"title":"Buy groceries ASAP"}'
# Delete todo
curl -X DELETE http://your-server:3000/todos/{id}
-H "Authorization: Bearer $TOKEN"
View interactive documentation at http://your-server:3000/openapi.
Getting Started
Ready to try GoREST?
Explore the full source code:
Found this helpful? Please star the project to show your support!
Summary
GoREST transforms your database schema into a production-ready REST API compiled as a single executable binary.
What you get:
- Complete CRUD operations for all tables
- JWT authentication with register/login endpoints
- Advanced filtering, ordering, pagination, and relationship expansion
- Interactive OpenAPI documentation
- Type-safe DTOs for security and flexibility
- Extensible hooks system for business logic
- Production-ready error handling and validation
Deployment benefits:
- Single self-contained binary with no dependencies
- Deploy anywhere: bare metal, Docker, Kubernetes, serverless, edge
- 10-20MB total size with sub-millisecond response times
- Instant startup and minimal resource footprint
- Simple CI/CD and version management
Your database schema becomes a production-grade REST API. Define your data model, run make generate && make build, and ship anywhere.
Contribute to GoREST
All contributions welcome – code, documentation, ideas, suggestions:
- Star the project
- Fork the project
- Report a bug
- Suggest a feature
- Submit a pull request
- Read contributing guidelines
Thanks for reading! 🚀
