Courses/Batteries Included
Standalone Course~30 min15 challenges

Batteries Included: Every Feature That Ships with Grit

Every Grit project ships with 18 production-ready features pre-configured and wired together. This course walks through every single one — what it is, why it matters, how it's configured, and how to use it. By the end, you'll know your project inside and out.


What "Batteries Included" Means

Most frameworks give you a foundation and tell you to install plugins for everything else. Need caching? Find a Redis library, wire it up, write middleware. Need email? Pick a service, install an SDK, build templates. Need file uploads? Choose between S3, Cloudflare, or local storage, configure presigned URLs, handle multipart forms. Every feature is a research project.

Batteries Included: A design philosophy where the framework ships with everything you need to build a production application out of the box. No hunting for plugins, no configuration marathons, no compatibility headaches. Every feature is pre-configured, tested, and wired together. Python's standard library and Ruby on Rails popularized this approach.

Grit ships 18 features pre-configured and wired together in every project. They work on day one — no setup required. And because they're modular, you can remove any feature you don't need without breaking the others.

Here's the full list of what ships with every Grit project:

#BatteryWhat It Does
1Redis CachingGET response caching with TTL
2File StorageS3-compatible uploads (MinIO/R2/S3)
3Email ServiceTransactional email with 4 HTML templates
4Background JobsRedis-backed async task queue
5Cron SchedulerRecurring tasks with cron expressions
6AI IntegrationVercel AI Gateway (hundreds of models)
7Two-Factor AuthTOTP + backup codes + trusted devices
8GORM StudioVisual database browser at /studio
9API DocsAuto-generated OpenAPI 3.1 at /docs
10SentinelWAF, rate limiting, security dashboard
11PulseRequest tracing, DB monitoring, metrics
12Grit UI100 shadcn-compatible components
13Docker SetupDev + production compose files
14JWT AuthAccess + refresh tokens, middleware
15CORS MiddlewarePre-configured cross-origin requests
16Gzip CompressionAutomatic response compression
17Request LoggingStructured request/response logs
18Connection PoolingOptimized DB connection management
You don't need to memorize all 18 features right now. This course walks through each one with examples and hands-on challenges. By the end, you'll have used every single battery in your project.
1

Challenge: Count the Setup Time

Look at the table above. Pick any 5 features and estimate how long it would take you to set up each one from scratch in a new Go project (finding the library, reading docs, writing code, testing). Add up the total hours. That's the "Setup Tax" Grit eliminates.

Redis Caching

Caching is one of the most impactful performance optimizations you can make. Instead of querying the database every time a user requests the same data, you store the result in memory and serve it instantly. Grit's cache service wraps Redis with a clean Go API.

Redis: An in-memory data store that operates at sub-millisecond speed. It stores data as key-value pairs in RAM, making reads hundreds of times faster than a database query. Redis is the industry standard for caching, session storage, and job queues.
Cache: A temporary storage layer that holds frequently accessed data closer to the consumer. Instead of computing a result or querying a database every time, you store the result once and serve it from the cache until it expires.
TTL (Time To Live): How long a cached value remains valid before it expires and gets deleted automatically. A TTL of 5 minutes means the cached data will be refreshed every 5 minutes. Shorter TTLs mean fresher data. Longer TTLs mean better performance.

Grit's cache service exposes three methods — Set, Get, and Delete:

internal/service/cache_service.go
// Store a value with a 5-minute TTL
cache.Set("user:1", userData, 5*time.Minute)

// Retrieve it — returns the cached value or an error if expired/missing
cached, err := cache.Get("user:1")

// Explicitly remove a cached value (e.g., after an update)
cache.Delete("user:1")

On top of the service, Grit includes cache middleware that automatically caches GET responses. When a GET request comes in, the middleware checks Redis first. If the response is cached, it returns it instantly without hitting your handler. If not, it calls your handler, caches the response, and returns it.

.env
# Redis configuration
REDIS_URL=redis://localhost:6379
The cache middleware only caches GET requests. POST, PUT, PATCH, and DELETE requests always go through to your handler. When a write happens, the relevant cache keys are automatically invalidated so users always see fresh data.
2

Challenge: Explore the Cache Service

Open the file internal/service/cache_service.go in your Grit project. What methods does it expose? What parameters does Set accept? What does Get return when the key doesn't exist?

File Storage (S3-Compatible)

Every real application needs file uploads — profile pictures, documents, images, attachments. Grit's file storage service works with any S3-compatible provider: MinIO for local development, AWS S3 for production, or Cloudflare R2 for edge storage.

S3-Compatible: Amazon S3 (Simple Storage Service) defined the standard API for object storage. Any service that implements this same API is called "S3-compatible." This means you can switch between providers (MinIO, R2, Backblaze B2) without changing your code — just update the endpoint URL.
Presigned URL: A temporary URL that grants time-limited access to upload or download a private file. Instead of routing files through your server (which uses bandwidth and memory), the client uploads directly to the storage provider using a presigned URL. The URL expires after a set time, so files stay secure.

The upload flow in Grit works like this:

Upload Flow
1. Client requests a presigned URL from your API
   POST /api/uploads/presign { filename: "photo.jpg" }

2. API generates a presigned URL (valid for 15 minutes)
   Response: { url: "https://storage.../photo.jpg?signature=...", key: "uploads/abc123.jpg" }

3. Client uploads the file directly to storage using the presigned URL
   PUT https://storage.../photo.jpg?signature=... (file data)

4. Client confirms the upload to your API
   POST /api/uploads/confirm { key: "uploads/abc123.jpg" }

5. API stores the file reference in the database

Configuration uses environment variables:

.env
# File storage (S3-compatible)
STORAGE_ENDPOINT=localhost:9000
STORAGE_ACCESS_KEY=minioadmin
STORAGE_SECRET_KEY=minioadmin
STORAGE_BUCKET=uploads
STORAGE_USE_SSL=false
STORAGE_REGION=us-east-1
In development, MinIO runs via Docker Compose. You can browse uploaded files atlocalhost:9001 using the MinIO Console. The default credentials are minioadmin / minioadmin.
3

Challenge: Upload a File

Start your Grit project with Docker Compose. Open the admin panel and upload a file (any image will do). Then open the MinIO Console at localhost:9001. Can you find the file in the uploads bucket? What is the full path of the file?

Email Service (Resend)

Transactional email is essential for any application — welcome emails, password resets, verification codes, notifications. Grit includes a complete email service powered by Resend with 4 pre-built HTML templates.

Transactional Email: Emails triggered by user actions — not marketing blasts. When someone registers, they get a welcome email. When they reset their password, they get a reset link. These are transactional emails. They're sent one-at-a-time in response to specific events.

Grit ships 4 HTML email templates:

  • Welcome — sent when a user registers
  • Password Reset — sent when a user requests a password reset
  • Email Verification — sent to verify a user's email address
  • Notification — a generic template for system notifications

In development, emails are captured by Mailhog instead of being sent to real inboxes. This means you can test every email flow without configuring a real email provider.

internal/service/email_service.go
// Send a welcome email to a new user
emailService.SendWelcome(user.Email, user.Name)

// Send a password reset link
emailService.SendPasswordReset(user.Email, resetToken)

// Send an email verification code
emailService.SendVerification(user.Email, verificationCode)

// Send a generic notification
emailService.SendNotification(user.Email, "Order Shipped", "Your order #1234 has shipped.")
.env
# Email (Resend)
RESEND_API_KEY=re_xxxxxxxxxxxx
EMAIL_FROM=noreply@myapp.com

# Local testing (Mailhog captures all emails)
SMTP_HOST=localhost
SMTP_PORT=1025
Mailhog runs automatically with Docker Compose. Open localhost:8025 in your browser to see every email your application sends during development.
4

Challenge: Catch a Welcome Email

Start your Grit project. Open Mailhog at localhost:8025. Now register a new user through the web app or API. Go back to Mailhog — do you see the welcome email? Open it and look at the HTML template. What information does it include?

Background Jobs (asynq)

Some tasks are too slow or too resource-intensive to run inside an HTTP request. Sending an email takes 200-500ms. Processing an image takes 1-2 seconds. Generating a report takes 5-10 seconds. You don't want users waiting for these. Background jobs let you offload work to a separate process that runs asynchronously.

Background Job: A task that runs outside the request/response cycle. Your API handler enqueues the job (pushes it onto a queue) and immediately returns a response to the user. A separate worker process picks up the job and executes it in the background. The user doesn't wait.

Grit uses asynq, a Redis-backed job queue for Go. It ships with 3 built-in workers:

  • Email Worker — sends transactional emails asynchronously
  • Image Worker — processes uploaded images (resize, thumbnails)
  • Cleanup Worker — removes expired tokens, old sessions, temp files

Dispatching a job is a single function call:

Dispatching a Background Job
// Enqueue an email job — returns immediately
err := jobs.Dispatch("email:welcome", map[string]interface{}{
    "email": user.Email,
    "name":  user.Name,
})

// Enqueue an image processing job
err := jobs.Dispatch("image:resize", map[string]interface{}{
    "file_key": "uploads/abc123.jpg",
    "width":    800,
    "height":   600,
})

// Enqueue with options: delay, retry, deadline
err := jobs.DispatchWithOptions("cleanup:tokens", nil, asynq.ProcessIn(1*time.Hour))

The admin panel includes a Jobs dashboard where you can monitor queued, active, completed, and failed jobs in real time.

5

Challenge: Inspect the Jobs Dashboard

Open the admin panel and navigate to the Jobs page. Are there any queued or completed jobs? Try registering a new user — the welcome email is sent as a background job. Go back to the Jobs page. Do you see the email job in the completed list?

Cron Scheduler

Background jobs handle one-off tasks. Cron jobs handle recurring tasks — things that need to happen on a schedule. Clean up expired tokens every hour. Send a weekly digest every Monday. Generate reports every night at midnight.

Cron Expression: A string that defines a schedule using 5 fields: minute, hour, day of month, month, day of week. For example, 0 9 * * 1 means "at 9:00 AM every Monday." The asterisk (*) means "every" — so * * * * * means every minute.

Grit's cron scheduler uses the same worker pool as background jobs, so you don't need a separate process. Here's the cron expression format:

Cron Expression Format
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
│ │ │ │ │
* * * * *

Common examples:

ExpressionSchedule
* * * * *Every minute
0 * * * *Every hour (at minute 0)
0 0 * * *Every day at midnight
0 9 * * 1Every Monday at 9:00 AM
0 0 1 * *First day of every month at midnight
30 2 * * 0Every Sunday at 2:30 AM
6

Challenge: Write Cron Expressions

Write the cron expression for each schedule:

  • Every hour on the hour
  • Every day at midnight
  • Every Monday at 9:00 AM
  • Every 15 minutes
  • The first of every month at 3:00 AM

Check your answers against the table above. The last two are not in the table — you'll need to figure them out yourself.

AI Integration (Vercel AI Gateway)

Every Grit project ships with AI integration powered by Vercel AI Gateway. One API key gives you access to hundreds of models from dozens of providers — Claude, GPT, Gemini, Llama, Mistral, and more. No provider-specific code required.

AI Gateway: A proxy service that sits between your application and AI providers. You send a standardized request to the gateway, and it routes it to whichever provider and model you choose. Switch models by changing one environment variable — no code changes.

Grit exposes 3 AI endpoints:

  • POST /api/ai/complete — single prompt in, single response out
  • POST /api/ai/chat — multi-turn conversation with message history
  • POST /api/ai/stream — streaming response (tokens arrive in real-time)
.env
# AI Gateway configuration
AI_GATEWAY_URL=https://gateway.ai.vercel.app/v1
AI_GATEWAY_API_KEY=your-api-key-here
AI_GATEWAY_MODEL=anthropic/claude-sonnet-4-6
Testing the Complete Endpoint
curl -X POST localhost:8080/api/ai/complete \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -d '{"prompt": "Explain REST APIs in 3 sentences."}'
The AI service is protected by authentication. You need a valid JWT token to call these endpoints. This prevents unauthorized users from consuming your AI credits.
7

Challenge: Test AI Integration

If you have an AI Gateway API key, add it to your .env file. Start the API, get a JWT token by logging in, then test the /api/ai/complete endpoint with a prompt of your choice. What model did the response come from? (Check the response headers or body for provider information.)

Two-Factor Authentication (TOTP)

Passwords alone are not enough. If someone steals a user's password (phishing, data breach, reuse), they can access the account. Two-Factor Authentication (2FA) adds a second layer — even with the password, an attacker needs the user's physical device.

TOTP (Time-based One-Time Password): An algorithm that generates a 6-digit code that changes every 30 seconds. The code is derived from a shared secret key and the current time. Both the server and the authenticator app know the secret, so they generate the same code independently. No internet connection needed — it's purely math-based.

Grit's 2FA system includes:

  • Authenticator app support — works with Google Authenticator, Authy, 1Password, Bitwarden
  • 10 backup codes — one-time-use codes in case the user loses their device
  • Trusted devices — skip 2FA on recognized devices for 30 days
  • Zero external dependencies — the TOTP algorithm runs in pure Go, no third-party services
2FA Setup Flow
1. User enables 2FA
   POST /api/auth/2fa/enable
   → Returns: QR code (base64) + secret key + 10 backup codes

2. User scans QR code with authenticator app

3. User verifies with the 6-digit code from the app
   POST /api/auth/2fa/verify { "code": "123456" }
   → 2FA is now active

4. On next login, after password is verified:
   POST /api/auth/2fa/validate { "code": "789012" }
   → Returns JWT token
Backup codes are hashed before storage (like passwords). When a user uses a backup code, it's marked as used and can't be used again. If all 10 codes are used, the user must generate new ones.
8

Challenge: Enable 2FA

Enable 2FA on your account. Scan the QR code with an authenticator app (Google Authenticator is free). Verify with a code. Then log out and log back in — you'll be prompted for the 6-digit code. Finally, test one of your backup codes: log out, and instead of the app code, enter a backup code. Does it work? Can you use the same backup code twice?

GORM Studio

GORM Studio is a visual database browser built directly into your Grit project. It runs at /studio and lets you browse tables, view and edit records, run raw SQL queries, and export schemas — all from your browser.

Database Browser: A graphical interface for inspecting and manipulating database contents. Instead of writing SQL in a terminal, you see tables, rows, and columns in a visual interface. Think of it like phpMyAdmin for PHP, but built specifically for GORM and embedded in your Go API.

What you can do in GORM Studio:

  • Browse tables — see all tables, columns, types, and relationships
  • View and edit data — click any row to view details, edit fields inline
  • Run SQL queries — execute raw SQL and see results in a table
  • Export schemas — download your database schema as SQL
Accessing GORM Studio
# Start your API
cd apps/api && go run cmd/server/main.go

# Open GORM Studio in your browser
http://localhost:8080/studio
GORM Studio is only available in development mode. In production, the /studio route is disabled for security. You don't want random users browsing your database.
9

Challenge: Explore GORM Studio

Open GORM Studio at localhost:8080/studio. Find at least 3 tables and describe what each one stores. How many columns does the users table have? Try running a SQL query: SELECT COUNT(*) FROM users;

API Documentation (Auto-Generated)

Grit automatically generates an OpenAPI 3.1 specification for your API and serves an interactive documentation UI at /docs. Every endpoint, every request body, every response shape is documented — and it stays in sync with your code because it's generated from your route definitions.

OpenAPI Specification: A standard format for describing REST APIs. It defines endpoints, request/response schemas, authentication methods, and more in a machine-readable JSON or YAML format. Tools like Swagger UI, Redoc, and Postman can read OpenAPI specs to generate interactive documentation.

No annotations or comments needed. Grit reads your Gin route definitions and GORM models to generate the spec automatically. When you add a new resource with grit generate, the docs update automatically.

Accessing API Docs
# Start your API
cd apps/api && go run cmd/server/main.go

# Interactive docs
http://localhost:8080/docs

# Raw OpenAPI 3.1 JSON spec
http://localhost:8080/docs/openapi.json
10

Challenge: Test an Endpoint

Open localhost:8080/docs. Find the login endpoint. Use the interactive UI to send a login request with the test credentials. Did you get a JWT token back? Now find the "list users" endpoint — does it require authentication? Try calling it without a token and see what error you get.

Security (Sentinel)

Sentinel is Grit's built-in security layer. It protects your API from common attacks without you writing a single line of security code. It includes a WAF (Web Application Firewall), rate limiting, brute-force protection, security headers, and a threat dashboard.

WAF (Web Application Firewall): A firewall that inspects HTTP requests for malicious patterns — SQL injection, cross-site scripting (XSS), path traversal, and other attack vectors. It sits between the internet and your application, blocking bad requests before they reach your handlers.
Rate Limiting: Restricting how many requests a client can make in a given time period. For example, 100 requests per minute per IP address. This prevents abuse, brute-force attacks, and denial-of-service attempts. Exceeding the limit returns a 429 Too Many Requests response.

What Sentinel protects against:

  • SQL Injection — malicious SQL in query parameters or request bodies
  • XSS (Cross-Site Scripting) — injecting JavaScript into your pages
  • Brute-force login — repeated login attempts are throttled and blocked
  • Path traversal — attempts to access files outside allowed directories
  • Missing security headers — auto-adds CSP, HSTS, X-Frame-Options, and more

The Sentinel dashboard at /sentinel/ui shows real-time threat data: blocked requests, top attacking IPs, attack types, and rate limit violations.

11

Challenge: Visit the Security Dashboard

Open localhost:8080/sentinel/ui in your browser. What information does the dashboard show? Make 50 rapid requests to any endpoint (use a loop in your terminal). Does the rate limiter kick in? Check the dashboard again for the rate limit data.

Observability (Pulse)

You can't fix what you can't see. Pulse is Grit's built-in observability layer. It tracks every request, monitors database performance, collects metrics, and runs health checks — all viewable from a dashboard at /pulse/ui.

Observability: The ability to understand what's happening inside your application by examining its outputs — logs, metrics, and traces. Observability answers questions like: "Why is this endpoint slow?" "How many requests per second am I handling?" "Is the database the bottleneck?"

What Pulse tracks:

  • Request tracing — every request is logged with timing, status code, and unique request ID
  • Database monitoring — query count, average duration, slowest queries
  • Metrics — requests per second, error rate, response time percentiles (p50, p95, p99)
  • Health checks — database connectivity, Redis connectivity, disk space, memory usage
Accessing Pulse
# Pulse dashboard
http://localhost:8080/pulse/ui

# Health check endpoint (JSON)
http://localhost:8080/pulse/health

# Metrics endpoint
http://localhost:8080/pulse/metrics
12

Challenge: Generate Some Metrics

Make at least 10 API requests — a mix of GET, POST, and invalid requests (wrong URLs, missing auth). Then open Pulse at localhost:8080/pulse/ui. What is the average response time? Which endpoint is the slowest? How many errors did you generate?

Grit UI Components

Every Grit project ships with access to 100 pre-built UI components in the shadcn/ui format. These aren't npm packages — they're source code files you install directly into your project, giving you full control to customize them.

The components are organized into 5 categories:

CategoryCountExamples
Marketing21Hero sections, feature grids, pricing tables, testimonials
Auth10Login forms, register forms, forgot password, 2FA pages
SaaS30Dashboard layouts, billing cards, settings pages, onboarding
E-commerce20Product cards, cart, checkout, order history, wishlists
Layout20Navbars, sidebars, footers, app shells, mobile menus
Browsing & Installing Components
# Browse all components in your browser
http://localhost:3000/components

# The registry API serves component metadata
GET /api/r.json          → lists all 100 components
GET /api/r/hero-split.json  → metadata + source for a specific component

# Install a component (shadcn CLI compatible)
npx shadcn@latest add "http://localhost:8080/r/hero-split.json"
13

Challenge: Browse the Registry

Open the component browser at localhost:3000/components (or call the API at localhost:8080/api/r.json). How many components are in the "saas" category? Pick one component and explain how you would install it into your project.

Docker Setup

Grit ships with two Docker Compose files: one for development and one for production. The development compose file starts all the infrastructure services your project needs with a single command.

Docker Compose: A tool for defining and running multi-container applications. Instead of starting each service manually (database, cache, storage, email), you define them all in a YAML file and start everything with docker compose up. Each service runs in its own isolated container.

The development compose file includes:

  • PostgreSQL — the primary database (port 5432)
  • Redis — caching and job queue (port 6379)
  • MinIO — S3-compatible file storage (port 9000, console on 9001)
  • Mailhog — email capture for testing (SMTP on 1025, UI on 8025)
Docker Commands
# Start all development services
docker compose up -d

# Check running services
docker compose ps

# View logs for a specific service
docker compose logs -f postgres

# Stop everything
docker compose down

# Stop and remove all data (fresh start)
docker compose down -v

The production compose file (docker-compose.prod.yml) is optimized for deployment — it builds your Go API into a minimal Docker image, uses environment variables for configuration, and includes health checks.

14

Challenge: List Running Services

Run docker compose up -d to start all services. Then run docker compose ps. List every running service, its port mapping, and its status. Visit each service's UI: PostgreSQL has no UI, but check MinIO at localhost:9001 and Mailhog at localhost:8025.

Summary

You've now toured every battery that ships with a Grit project. Here's the complete list:

  • Redis Caching — sub-millisecond response caching with automatic invalidation
  • File Storage — S3-compatible uploads with presigned URLs (MinIO, R2, S3)
  • Email Service — 4 HTML templates with Resend (Mailhog for local testing)
  • Background Jobs — Redis-backed async task queue with 3 built-in workers
  • Cron Scheduler — recurring tasks with cron expressions, same worker pool
  • AI Integration — Vercel AI Gateway with hundreds of models, 3 endpoints
  • Two-Factor Auth — TOTP with authenticator apps, backup codes, trusted devices
  • GORM Studio — visual database browser at /studio
  • API Documentation — auto-generated OpenAPI 3.1 spec at /docs
  • Sentinel — WAF, rate limiting, brute-force protection, security dashboard
  • Pulse — request tracing, DB monitoring, metrics, health checks
  • Grit UI — 100 shadcn-compatible components across 5 categories
  • Docker Setup — dev compose (4 services) + production compose
  • JWT Authentication — access + refresh tokens with middleware
  • CORS Middleware — pre-configured cross-origin request handling
  • Gzip Compression — automatic response compression
  • Request Logging — structured logs with request IDs
  • Connection Pooling — optimized database connection management
15

Challenge: Tour Every Tool

Visit each of these URLs in your running Grit project and confirm they work:

  • localhost:8080/studio — GORM Studio
  • localhost:8080/docs — API Documentation
  • localhost:8080/pulse/ui — Pulse Observability
  • localhost:8080/sentinel/ui — Sentinel Security
  • localhost:8025 — Mailhog (email capture)
  • localhost:9001 — MinIO Console (file storage)

Take a screenshot of each. You now know every tool in your Grit project. These aren't plugins you installed — they shipped with your project from the very first command.