Grout is a small HTTP service that renders SVG avatars with user initials and rectangular placeholder images. It relies on the github.com/fogleman/gg drawing library and embeds Go fonts for crisp text output.
docker compose up --buildgo run ./cmd/groutThe server listens on :8080 by default and exposes the routes below.
Generates a square avatar that displays the initials derived from the provided name.
- Path:
/avatar/{name}[.ext]whereextcan besvg,png,jpg,jpeg,gif, orwebp. You can also use thenamequery parameter. - Format: Images are served as SVG by default when no extension is specified. Use
.svg,.png,.jpg,.jpeg,.gif, or.webpextension to request a specific format. - Size:
sizequery parameter (default128), applied to both width and height. - Background Color:
backgroundorbgquery parameter accepts hex (f0e9e9) or the literalrandomto derive a deterministic color per name. - Text Color:
colorquery parameter (hex, default auto-contrasted). - Rounded:
rounded=truedraws a circle instead of a square. - Bold:
bold=trueswitches to the embedded Go Bold font.
Examples:
# Default SVG format
curl "http://localhost:8080/avatar/Jane+Doe?size=256&rounded=true&bold=true&background=random"
# SVG format (explicit)
curl "http://localhost:8080/avatar/Jane+Doe.svg?size=256&rounded=true&bold=true&background=random"
# PNG format
curl "http://localhost:8080/avatar/Jane+Doe.png?size=256&rounded=true&bold=true&background=random"
# JPG format
curl "http://localhost:8080/avatar/Jane+Doe.jpg?size=256"
# WebP format
curl "http://localhost:8080/avatar/Jane+Doe.webp?size=256"
# Using 'bg' parameter (shorthand for background)
curl "http://localhost:8080/avatar/Jane+Doe?size=256&bg=ff5733"Creates a rectangular placeholder image with custom dimensions and optional overlay text. Supports automatic text wrapping for long content like quotes and jokes.
- Path Form:
/placeholder/{width}x{height}[.ext]whereextcan besvg,png,jpg,jpeg,gif, orwebp. If extension is omitted, images are served as SVG by default. - Format: Images are served as SVG by default when no extension is specified. Use
.svg,.png,.jpg,.jpeg,.gif, or.webpextension to request a specific format. - Dimensions: Can also use query parameters
wandh(default128). - Text:
textquery parameter (defaults to "{width} x {height}"). - Quote:
quote=truequery parameter to use a random quote instead of custom text. Requires minimum width of 300px. - Joke:
joke=truequery parameter to use a random joke instead of custom text. Requires minimum width of 300px. - Category:
categoryquery parameter to filter quotes/jokes by category (optional). - Background Color:
backgroundorbgquery parameter (hex, defaultcccccc). Supports gradients with comma-separated colors (e.g.,ff0000,0000fffor red to blue). - Text Color:
colorquery parameter (hex, default auto-contrasted).
Text Rendering Features:
- Automatic text wrapping for quotes and jokes based on image width
- Content is centered with 10% padding on all sides
- Dynamic font sizing (16px-48px) based on text length and image dimensions
- Multi-line text support with 1.5x line spacing for readability
inspirational- Inspirational quotes to motivate and upliftmotivational- Motivational quotes for taking actionlife- Quotes about life and livingsuccess- Quotes about achieving successwisdom- Wise sayings and philosophical thoughtslove- Quotes about love and relationshipshappiness- Quotes about finding joy and happinesstechnology- Quotes about technology and innovation
programming- Developer and programming jokesscience- Scientific and chemistry jokesdad- Classic dad jokespuns- Wordplay and punstechnology- Technology and computer jokeswork- Work and office humoranimals- Animal-related jokesgeneral- General purpose jokes
Examples:
# Default SVG format
curl "http://localhost:8080/placeholder/800x400?text=Hero+Image&background=222222&color=f5f5f5"
# SVG format (explicit)
curl "http://localhost:8080/placeholder/800x400.svg?text=Hero+Image&background=222222&color=f5f5f5"
# PNG format (using 'bg' shorthand)
curl "http://localhost:8080/placeholder/800x400.png?text=Hero+Image&bg=222222&color=f5f5f5"
# JPG format
curl "http://localhost:8080/placeholder/1200x600.jpg?text=Banner"
# GIF format
curl "http://localhost:8080/placeholder/400x400.gif"
# Gradient background (red to blue, SVG)
curl "http://localhost:8080/placeholder/800x400?bg=ff0000,0000ff&text=Gradient"
# Gradient background (green to yellow, PNG)
curl "http://localhost:8080/placeholder/1200x600.png?bg=00ff00,ffff00"
# Random quote (any category) - text wraps automatically
curl "http://localhost:8080/placeholder/1200x400?quote=true"
# Random inspirational quote with custom colors
curl "http://localhost:8080/placeholder/1200x400?quote=true&category=inspirational&bg=2c3e50&color=ecf0f1"
# Random programming joke
curl "http://localhost:8080/placeholder/800x600.png?joke=true&category=programming"
# Random joke with custom colors
curl "http://localhost:8080/placeholder/1000x500?joke=true&bg=2c3e50&color=ecf0f1"- Images are served as SVG by default (when no extension is specified). The
Content-Typeheader is set based on the requested format:image/svg+xml,image/webp,image/png,image/jpeg, orimage/gif. - Successful responses include
Cache-Control: public, max-age=31536000, immutableand anETagkeyed by the query parameters and format. - Cached entries are stored in an in-memory LRU (
CacheSize = 2000) to reduce rendering overhead. Cache hits expose the headerX-Cache: HIT.
If generation fails (for example due to invalid parameters), the server responds with HTTP 500 and Failed to generate image. Invalid dimensions fallback to safe defaults to keep the server responsive.
ADDRenv var or-addrflag controls the HTTP bind address (default:8080).CACHE_SIZEenv var or-cache-sizeflag sets LRU entry count (default2000).DOMAINenv var or-domainflag sets the public domain for example URLs in the home page (defaultlocalhost:8080).STATIC_DIRenv var or-static-dirflag sets the directory for static files likerobots.txtandsitemap.xml(default./static).RATE_LIMIT_RPMenv var or-rate-limit-rpmflag sets the rate limit in requests per minute per IP (default100).RATE_LIMIT_BURSTenv var or-rate-limit-burstflag sets the burst size for the rate limiter (default10).
Grout implements per-IP rate limiting to prevent DoS attacks. By default:
/avatar/and/placeholder/endpoints are rate limited to 100 requests per minute per IP with a burst of 10- Static assets (
/favicon.ico,/robots.txt,/sitemap.xml) and the health endpoint (/health) are not rate limited - Rate limiting is based on client IP, respecting
X-Forwarded-ForandX-Real-IPheaders for proxy scenarios - When the rate limit is exceeded, the server returns HTTP
429 Too Many Requests
To adjust the rate limits, set the environment variables or use command-line flags:
# Allow 200 requests per minute with burst of 20
RATE_LIMIT_RPM=200 RATE_LIMIT_BURST=20 go run ./cmd/groutWhen using Docker Compose, you can override environment variables in docker-compose.yml:
environment:
ADDR: ":3000"
CACHE_SIZE: "5000"
DOMAIN: "grout.example.com"
STATIC_DIR: "/app/static"
RATE_LIMIT_RPM: "200"
RATE_LIMIT_BURST: "20"The application serves static files (like robots.txt and sitemap.xml) from the configured STATIC_DIR directory. If files are not found in this directory, the application falls back to embedded default versions.
To customize static files:
- Create a
staticdirectory (or use the default location) - Add your customized
robots.txtand/orsitemap.xmlfiles - These files support the
{{DOMAIN}}placeholder, which will be replaced with the configured domain
Docker Deployment:
For persistent static files in Docker, mount a volume:
services:
grout:
volumes:
- ./static:/app/staticThis ensures your customizations persist across container restarts and updates. The embedded files serve as fallbacks if custom files are not provided.
go build -o grout ./cmd/groutdocker build -t grout .docker run -p 8080:8080 -e ADDR=":8080" -e DOMAIN="grout.example.com" groutThe project includes GitHub Actions workflows that automatically:
Runs on every pull request and push to main/master:
- Tests: Runs all unit tests with race detection and coverage reporting
- Lint: Runs
golangci-lintfor code quality checks - Format: Verifies code is properly formatted with
go fmt - Vet: Runs
go vetto catch common issues - Coverage: Optionally uploads coverage to Codecov (requires
CODECOV_TOKENsecret)
To enable Codecov integration (optional):
CODECOV_TOKEN: Your Codecov upload token
- Customize the defaults by editing the constants in
internal/config/config.go. - Extend
DrawImageininternal/render/render.goif you need additional shapes, padding, or font scaling strategies. - Consider fronting the service with a CDN when deploying to production so the long-lived cache headers are effective.
The project includes comprehensive unit and integration tests:
# Run all tests (unit + integration)
go test ./...
# Run tests with race detection and coverage
go test -race -coverprofile=coverage.out ./...
# Run only unit tests (skip integration tests)
go test -short ./...
# Run only integration tests
go test ./internal/handlers -run TestIntegration
# Run tests with verbose output
go test -v ./...
# Run benchmarks
go test -bench=. ./...Integration tests start a real HTTP server and make actual HTTP requests to verify end-to-end functionality. They are fast enough for CI (complete in ~2 seconds) and can be skipped during development with the -short flag.
For more information about the project:
- CONTRIBUTING.md - Guidelines for contributing to the project
- CODE_OF_CONDUCT.md - Community standards and expectations
- SECURITY.md - Security policy and vulnerability reporting
- ARCHITECTURE.md - Technical architecture and design decisions
- CHANGELOG.md - Project changelog and version history
- LICENSE - MIT License for open-source commercial use
We welcome contributions! Please read our Contributing Guidelines before submitting pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.