Skip to content

nicolasbonnici/gorest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

228 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

GoREST

Test Go Report Card License

πŸš€ GoREST is a Go library for building type-safe REST APIs in Go from your existing database schema or from scratch.

Warning

Pre-1.0 Development: The API is not yet stabilized and may introduce breaking changes until v1.0.0 is released. Pin to a specific version tag and test thoroughly before production use.

✨ Features

  • ⚑ Type-safe generic CRUD operations with hooks system
  • πŸš€ Unified processor module eliminating handler boilerplate - handlers become one-liners with automatic RBAC, validation, pagination, filtering & serialization
  • πŸ”§ Fluent SQL query builder with database abstraction
  • πŸ”‘ Built-in JWT authentication with user management, registration, login, and token refresh
  • πŸ” RBAC Role based access control layer with field-level permissions and voter system
  • 🧐 Audit log
  • βœ… Security best practices, rate limiting, CORS, response compression and many more configurable core middleware
  • 🎭 Hook layer to add your business logic and override any API layer
  • 🧩 Modular plugin system that can add features, override some or all existing endpoints or even CLI commands
  • πŸ›  Code generation plugin for REST endpoints, DTOs and models (gorest-codegen)
  • 🌐 JSON-LD support with semantic web context (@context, @type, @id)
  • πŸ”— Advanced resource deserialization with IRI and optional on demand relations
  • πŸ” Advanced serialization, filtering & ordering
  • πŸ“„ Page based pagination with Hydra collections
  • πŸ‘¨πŸ»β€πŸ’» DAL, migrations and fixtures with PostgreSQL, MySQL and SQLite engines support
  • πŸ›‘οΈ Production grade errors and processes management
  • 🏷️ API versioning
  • πŸ“œ OpenAPI 3 spec generation (gorest-openapi)
  • πŸ’š Status endpoint for health check (gorest-status)
  • 🐳 Docker and Kubernetes support
  • πŸ§ͺ Full test coverage with automated testing

πŸš€ Quick Start

1. Create Your Project

mkdir my-api && cd my-api
go mod init github.com/yourusername/my-api
go get github.com/nicolasbonnici/gorest@latest

2. Install Development Environment

First you need Go 1.25+ installed, then run:

make install

That's it! Your development environment is now set up.

3. Configure Your API

Create gorest.yaml in your project root:

server:
  scheme: "${SERVER_SCHEME:-http}"
  host: "${SERVER_HOST:-localhost}"
  port: "${SERVER_PORT:-8000}"
  environment: "${ENV:-development}"
  cors_origins: "${CORS_ORIGINS:-*}"
  compression_enabled: true  # gzip/deflate/brotli support (default: true)
  compression_level: 2       # 1=speed, 2=balanced, 3=best compression


database:
  url: "${DATABASE_URL}"

pagination:
  default_limit: "${PAGINATION_DEFAULT_LIMIT:-10}"
  max_limit: "${PAGINATION_MAX_LIMIT:-1000}"

auth:
  enabled: true
  jwt_secret: "${JWT_SECRET}"
  jwt_ttl: 900

plugins:
  - name: openapi
    enabled: true
    config:
      title: "My API"
      version: "1.0.0"

Set required environment variables:

export ENV="production"              # default: development
export DATABASE_URL="postgres://user:pass@localhost:5432/mydb?sslmode=require"
export SERVER_SCHEME="https"         # default: http
export SERVER_HOST="api.example.com" # default: localhost
export SERVER_PORT="8080"            # default: 8000
export JWT_SECRET=$(openssl rand -base64 32)
export CORS_ORIGINS="localhost:8000" # default: *
export PAGINATION_DEFAULT_LIMIT="20" # default: 10
export PAGINATION_MAX_LIMIT="5000"   # default: 1000

Or use a .env file (dotenv> support):

ENV=production
DATABASE_URL=postgres://user:pass@localhost:5432/mydb?sslmode=require
SERVER_SCHEME=https
SERVER_HOST=api.example.com
SERVER_PORT=8080
JWT_SECRET=your-secret-key-here
CORS_ORIGINS=example.com
PAGINATION_DEFAULT_LIMIT=20
PAGINATION_MAX_LIMIT=5000

πŸ“š Full configuration documentation β†’

Environment Variable Interpolation

GoREST supports bash-style environment variable interpolation with default fallback values:

Syntax:

  • ${VAR} - Use environment variable VAR (leaves ${VAR} unchanged if not set)
  • ${VAR:-default} - Use environment variable VAR, or "default" if not set

Examples:

server:
  # Will use environment variable or fallback to default
  port: "${SERVER_PORT:-8000}"
  host: "${SERVER_HOST:-localhost}"

  # Required variable (no default)
  environment: "${ENV}"

database:
  # Complex defaults work too
  url: "${DATABASE_URL:-postgres://user:pass@localhost:5432/dev?sslmode=disable}"

pagination:
  # Numeric defaults
  default_limit: "${PAGINATION_DEFAULT_LIMIT:-10}"
  max_limit: "${PAGINATION_MAX_LIMIT:-1000}"

auth:
  # Empty default
  jwt_secret: "${JWT_SECRET:-}"

Behavior:

  • If the environment variable is set (even to an empty string), its value is used
  • If the environment variable is not set and a default is provided, the default is used
  • If the environment variable is not set and no default is provided, the original ${VAR} string remains

Note: Environment variable interpolation only works for string fields in the configuration. Integer fields like port, default_limit, and max_limit must be specified as numeric values directly in the YAML file.

3. Create Your Main Application

You can either write your routes manually or use the gorest-codegen plugin to generate them from your database schema.

Manual approach:

package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/nicolasbonnici/gorest"
    "github.com/nicolasbonnici/gorest/crud"
    "github.com/nicolasbonnici/gorest/database"
)

type User struct {
    ID    string `json:"id" db:"id"`
    Email string `json:"email" db:"email"`
}

func (User) TableName() string { return "users" }

func main() {
    cfg := gorest.Config{
        ConfigPath: ".",
        RegisterRoutes: func(router fiber.Router, db database.Database, paginationLimit, paginationMaxLimit int, pluginRegistry *plugin.PluginRegistry) {
            userCRUD := crud.New[User](db)
            router.Get("/users", func(c *fiber.Ctx) error {
                result, _ := userCRUD.GetAllPaginated(c.Context(), crud.PaginationOptions{Limit: 10})
                return c.JSON(result.Items)
            })
        },
    }
    gorest.Start(cfg)
}

With code generation plugin:

# Install and run gorest-codegen
go run github.com/nicolasbonnici/gorest-codegen/cmd/codegen@latest all
package main

import (
    "github.com/yourusername/my-api/generated/resources"
    "github.com/nicolasbonnici/gorest"
)

func main() {
    cfg := gorest.Config{
        ConfigPath:     ".",
        RegisterRoutes: resources.RegisterGeneratedRoutes,
    }
    gorest.Start(cfg)
}

4. Run Your API

go run main.go

Your API is now running at: ${SERVER_SCHEME}://${SERVER_HOST}:${SERVER_PORT}/v1.0.0/

  • πŸ“š API specs: ${SERVER_SCHEME}://${SERVER_HOST}:${SERVER_PORT}/v1.0.0/openapi
  • πŸ’š Status: ${SERVER_SCHEME}://${SERVER_HOST}:${SERVER_PORT}/v1.0.0/status

(With default values: http://localhost:8000/v1.0.0/)

Note: Development builds default to /v1.0.0 prefix. Use build-time version injection for production deployments.


πŸ“¦ GoREST usage

Import GoREST library directly in your Go projects:

go get github.com/nicolasbonnici/gorest@latest

Available Packages

Package Description
processor Unified API processor eliminating handler boilerplate
query Type-safe SQL query builder
crud Type-safe CRUD operations with hooks
database Multi-database abstraction
expand Relation expansion (IRI to object)
filter Query filtering & ordering
serializer JSON-LD response serialization
hooks Lifecycle hooks for business logic
plugin Plugin interfaces
pluginloader Plugin factory & loading
pagination Hydra-compliant pagination
response HTTP response helpers
migrations Database migration system
fixtures Test fixture management

Example

import (
    "github.com/gofiber/fiber/v2"
    "github.com/nicolasbonnici/gorest/database"
    "github.com/nicolasbonnici/gorest/crud"
    authpkg "github.com/nicolasbonnici/gorest/auth"
)

type User struct {
    ID    string `json:"id" db:"id"`
    Email string `json:"email" db:"   email"`
}

func (User) TableName() string { return "users" }

func main() {
    db, _ := database.Open("postgres", "postgres://...")
    defer db.Close()

    userCRUD := crud.New[User](db)
    router := fiber.New()

    router.Get("/users", auth.RequireAuth("secret", func(c *fiber.Ctx) error {
        ctx := auth.Context(c)
        result, _ := userCRUD.GetAllPaginated(ctx, crud.PaginationOptions{Limit: 10})
        return c.JSON(result.Items)
    }))

    router.Listen(":8000")
}

πŸ“š Full API documentation on pkg.go.dev


πŸ“š Core Documentation

Configuration & Setup

API Development

  • Processor β†’ - Unified API processor eliminating CRUD boilerplate with one-liner handlers

Data Management

Business Logic


πŸ” Quick Examples

Query Builder

import "github.com/nicolasbonnici/gorest/query"

// Build type-safe SQL queries
sql, args := query.New(db.Dialect()).
    Select("id", "name", "email").
    From("users").
    Where(query.Eq("status", "active")).
    Where(query.Gt("age", 18)).
    OrderBy("created_at", query.DESC).
    Limit(10).
    Build()

// Use in hooks without string manipulation
func (h *PostHooks) ModifySelectQuery(ctx context.Context, op hooks.Operation, builder *query.SelectBuilder) (*query.SelectBuilder, bool) {
    if !isAuthenticated(ctx) {
        builder = builder.Where(query.Eq("status", "published"))
        return builder, true
    }
    return builder, false
}

πŸ“š Full query builder documentation β†’

Processor Pattern

import "github.com/nicolasbonnici/gorest/processor"

// Create processor with configuration
proc := processor.New(processor.ProcessorConfig[
    models.Todo,
    dtos.TodoCreateDTO,
    dtos.TodoUpdateDTO,
    dtos.TodoResponseDTO,
]{
    DB:                 db,
    CRUD:               crud.New[models.Todo](db),
    Converter:          &converters.TodoConverter{},
    PaginationLimit:    20,
    PaginationMaxLimit: 100,
    AllowedFields:      []string{"id", "title", "status", "created_at"},
}).
    WithCreateHook(hooks.CreateHook).
    WithUpdateHook(hooks.UpdateHook)

// Handlers become one-liners
type TodoResource struct {
    processor processor.Processor[models.Todo, dtos.TodoCreateDTO, dtos.TodoUpdateDTO, dtos.TodoResponseDTO]
}

func (r *TodoResource) Create(c *fiber.Ctx) error  { return r.processor.Create(c) }
func (r *TodoResource) GetAll(c *fiber.Ctx) error  { return r.processor.GetAll(c) }
func (r *TodoResource) GetByID(c *fiber.Ctx) error { return r.processor.GetByID(c) }
func (r *TodoResource) Update(c *fiber.Ctx) error  { return r.processor.Update(c) }
func (r *TodoResource) Delete(c *fiber.Ctx) error  { return r.processor.Delete(c) }

πŸ“š Full processor documentation β†’

Filtering & Ordering

# Filter by status
GET /v1.0.0/todos?status=active

# Multiple filters with comparison
GET /v1.0.0/todos?status=active&priority[gte]=5

# Order results
GET /v1.0.0/todos?order[createdAt]=desc

# Combine all
GET /v1.0.0/todos?status=active&priority[gte]=5&order[createdAt]=desc&limit=10

πŸ“š Full filtering documentation β†’

Expand Relations

# IRI reference (default)
GET /v1.0.0/todos/123
# Returns: { "user": "/v1.0.0/users/456", ... }

# Expand to full object
GET /v1.0.0/todos/123?expand[]=user
# Returns: { "user": { "id": "456", "name": "Alice", ... }, ... }

πŸ“š Full expansion documentation β†’

JSON-LD Support

# Regular JSON
curl -H "Accept: application/json" http://localhost:8000/v1.0.0/todos/123

# JSON-LD with semantic context
curl -H "Accept: application/ld+json" http://localhost:8000/v1.0.0/todos/123

# Production with custom version
curl -H "Accept: application/json" https://api.example.com/v2.1.0/todos/123

πŸ“š Full JSON-LD documentation β†’


πŸ“‚ Project Structure

Basic Project

my-api/
β”œβ”€β”€ gorest.yaml              # Configuration
└── main.go                  # Your application

With Code Generation Plugin (optional)

my-api/
β”œβ”€β”€ gorest.yaml              # Configuration
β”œβ”€β”€ main.go                  # Your application
└── generated/               # Generated by gorest-codegen plugin
    β”œβ”€β”€ models/              # DB models
    β”œβ”€β”€ resources/           # REST handlers
    β”œβ”€β”€ dtos/                # Data transfer objects
    └── openapi/             # OpenAPI schema

GoREST Library

gorest/
β”œβ”€β”€ processor/               # Unified API processor
β”œβ”€β”€ auth/                    # Built-in authentication layer
β”œβ”€β”€ crud/                    # Generic CRUD
β”œβ”€β”€ database/                # Multi-DB abstraction
β”‚   β”œβ”€β”€ postgres/
β”‚   β”œβ”€β”€ mysql/
β”‚   └── sqlite/
β”œβ”€β”€ migrations/              # Migration system
β”œβ”€β”€ fixtures/                # Fixture management
β”œβ”€β”€ expand/                  # Relation expansion
β”œβ”€β”€ filter/                  # Query filtering
β”œβ”€β”€ serializer/              # JSON-LD serialization
β”œβ”€β”€ hooks/                   # Lifecycle hooks
β”œβ”€β”€ plugin/                  # Plugin interfaces
β”œβ”€β”€ pluginloader/            # Plugin loading
β”œβ”€β”€ middleware/              # Core middleware
β”œβ”€β”€ pagination/              # Hydra pagination
└── response/                # HTTP helpers

πŸ›  Development Commands

# Testing
make test-up          # Start test databases
make test-schema      # Load test schema
make test             # Run all tests
make test-coverage    # Run tests with coverage

Code Generation Plugin: See gorest-codegen for generating models, resources, DTOs and OpenAPI specs from your database schema.


πŸš€ Production Deployment

Docker

Using GoREST status plugin gorest-status

# Dockerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY . .
# Inject version from git tag at build time
RUN go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=$(git describe --tags --always)" -o api

FROM alpine:latest
RUN apk --no-cache add ca-certificates curl
WORKDIR /root/
COPY --from=builder /app/api .
CMD ["./api"]
# docker-compose.yml
services:
  api:
    build:
      context: .
      args:
        VERSION: v1.0.0  # Or use git describe --tags
    ports: ["8000:8000"]
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - JWT_SECRET=${JWT_SECRET}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/v1.0.0/status"]
      interval: 30s
      timeout: 3s
      retries: 3

Nginx Reverse Proxy

upstream gorest {
    server localhost:8000;
}

server {
    listen 443 ssl http2;
    server_name api.example.com;

    # SSL configuration
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # Redirect root to latest versioned API docs
    location = / {
        return 301 https://$host/v2.0.0/openapi;
    }

    # Proxy all versioned routes
    location / {
        proxy_pass http://gorest;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Recommended headers
        proxy_set_header X-Request-ID $request_id;
        proxy_buffering off;
    }
}

Kubernetes Health Checks

livenessProbe:
  httpGet:
    path: /v1.0.0/status
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 30

readinessProbe:
  httpGet:
    path: /v1.0.0/status
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 10

🏷️ API Versioning

GoREST automatically versions your API routes based on your git tags with zero configuration required. All routes are prefixed with the version for consistency across development, staging, and production environments.

How It Works

All routes are automatically prefixed with the version for consistency across all environments:

Environment Build Command Version Variable Route Example
Development go build dev β†’ v1.0.0 (fallback) /v1.0.0/posts
Staging go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=v1.0.0-rc1" v1.0.0-rc1 /v1.0.0-rc1/posts
Production go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=v2.3.1" v2.3.1 /v2.3.1/posts
From Git go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=$(git describe --tags)" Git tag /v1.2.3/posts

Fallback behavior: If no version is provided or version is "dev", routes default to /v1.0.0 prefix.

Build with Version

# Development build (defaults to v1.0.0)
go build
# Routes: /v1.0.0/posts, /v1.0.0/health, /v1.0.0/login

# Production build with version from git tag
go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=$(git describe --tags --always)"
# Routes: /v1.2.3/posts, /v1.2.3/health, /v1.2.3/login

# Or use a specific version
go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=v2.0.0"
# Routes: /v2.0.0/posts, /v2.0.0/health, /v2.0.0/login

Example

# Development build (defaults to v1.0.0)
go build
curl http://localhost:8000/v1.0.0/posts

# Production build with version v2.0.0
go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=v2.0.0"
curl http://localhost:8000/v2.0.0/posts

What Gets Versioned

All routes are automatically versioned, including:

  • User routes: Your application endpoints (e.g., /v1.0.0/posts, /v2.0.0/users)
  • Plugin endpoints: Built-in plugin routes (e.g., /v1.0.0/health, /v1.0.0/login, /v1.0.0/openapi)

Benefits

βœ… Consistent across environments - Same URL structure in dev, staging, and production (all use proper semantic versions) βœ… Easy API evolution - Support multiple API versions simultaneously βœ… Clear versioning - Version is always visible in the URL βœ… Zero configuration - Defaults to v1.0.0, or use build-time version injection βœ… No weird prefixes - Always uses proper semantic versioning (v1.0.0, v2.1.3, etc.)

Multiple API Versions

To support multiple API versions simultaneously, deploy separate instances with different version builds:

# Build v1.0.0
go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=v1.0.0" -o api-v1

# Build v2.0.0 with breaking changes
go build -ldflags "-X github.com/nicolasbonnici/gorest.Version=v2.0.0" -o api-v2

# Run both on different ports
PORT=8001 ./api-v1 &  # Serves /v1.0.0/*
PORT=8002 ./api-v2 &  # Serves /v2.0.0/*

Use a reverse proxy to route by version prefix:

# Route v1 requests
location /v1.0.0/ {
    proxy_pass http://localhost:8001/v1.0.0/;
}

# Route v2 requests
location /v2.0.0/ {
    proxy_pass http://localhost:8002/v2.0.0/;
}

# Default to latest version
location / {
    proxy_pass http://localhost:8002/v2.0.0/;
}

πŸ”’ Security

  • Passwords: bcrypt hashing with automatic salts
  • JWT: 32+ character secrets required
  • CORS: Configurable origins
  • Rate Limiting: Configurable per-IP limits
  • SQL Injection: Parameterized queries
  • Input Validation: go-playground/validator support
  • Security Headers: X-Frame-Options, C SP, HSTS, etc.

Git Hooks

This directory contains git hooks for the GoREST project to maintain code quality.

Available Hooks

pre-commit

Runs before each commit to ensure code quality:

  • Linting: Runs make lint to check code style and potential issues
  • Tests: Runs make test to verify all tests pass

Installation

Automatic Installation

Run the install script from the project root:

./.githooks/install.sh

Manual Installation

Copy the hooks to your .git/hooks directory:

cp .githooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

🀝 Contributing

See CONTRIBUTING.md for guidelines.

Quick start:

git clone https://github.com/YOUR_USERNAME/gorest.git
cd gorest
make test-up && make test-schema && make test

πŸ“‹ Changelog

See CHANGELOG.md for release history.


πŸ“œ License

MIT – free to use in your projects πŸš€

About

GoREST is a library that helps you easily create production-grade REST APIs

Topics

Resources

License

Contributing

Stars

9 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages