A Rails API that vends a list of apps in JSON format, designed to be consumed by browser-based and iOS clients.
- Ruby 3.3.6
- Bundler
# Install dependencies
bundle install
# Set up the database
bin/rails db:create db:migrate db:seed
# Start the server
bin/rails serverThe API will be available at http://localhost:3000.
All endpoints are namespaced under /v1/.
| Method | Path | Description |
|---|---|---|
GET |
/v1/apps |
List all apps (paginated) |
GET |
/v1/apps?q=search |
Search apps by name or tagline |
GET |
/v1/apps/:id |
Fetch a single app |
GET |
/up |
Health check |
# List all apps
curl http://localhost:3000/v1/apps
# Search for an app
curl "http://localhost:3000/v1/apps?q=picnic"
# Paginate (page 2, 5 per page)
curl "http://localhost:3000/v1/apps?page=2&per_page=5"
# Fetch a single app
curl http://localhost:3000/v1/apps/1
# Health check
curl http://localhost:3000/upSearch is case-insensitive and matches against both name and tagline fields. Pass the q parameter:
GET /v1/apps?q=picnic
Returns all apps where "picnic" appears anywhere in the name or tagline.
The list endpoint returns paginated results by default:
page— page number (default: 1, minimum: 1)per_page— results per page (default: 20, maximum: 100)
The meta object in the response includes page, per_page, and total so clients can render pagination controls.
Success — list (200)
{
"apps": [
{
"id": 1,
"name": "Picnic Basket",
"tagline": "The best way to fill up your picnic basket!",
"image_url": "https://picsum.photos/seed/picnic-basket/200"
}
],
"meta": {
"page": 1,
"per_page": 20,
"total": 8
}
}Success — single app (200)
{
"id": 1,
"name": "Picnic Basket",
"tagline": "The best way to fill up your picnic basket!",
"image_url": "https://picsum.photos/seed/picnic-basket/200"
}All errors return a JSON object with an error key.
| Status | Meaning | Example |
|---|---|---|
400 |
Invalid parameters (array params, search query too long) | { "error": "Invalid parameter: q" } |
404 |
App not found or unmatched route | { "error": "App not found" } |
500 |
Unexpected server error | { "error": "Internal server error" } |
503 |
Health check failed (DB unreachable) | { "error": "Database connection failed" } |
bundle exec rspecCovers request specs for all endpoints (pagination, search, input validation, error handling, health check) and model specs for validations and the search scope.
# Static security analysis
bundle exec brakeman -q
# Style linting
bundle exec rubocopGitHub Actions runs on every push to main and on pull requests:
- Tests — RSpec suite with database setup
- Security — Brakeman (static analysis) and bundler-audit (gem vulnerabilities)
- Linting — RuboCop for consistent style
app/
controllers/
application_controller.rb # Global error handling, catch-all 404
health_controller.rb # GET /up — DB connectivity check
v1/
apps_controller.rb # GET /v1/apps, GET /v1/apps/:id
models/
app.rb # Validations, search scope
serializers/
app_serializer.rb # Explicit API response contract (Blueprinter)
db/
migrate/ # Schema migrations
seeds.rb # Sample app data
spec/
models/app_spec.rb # Model and search specs
requests/apps_spec.rb # API endpoint specs
requests/health_spec.rb # Health check specs
factories/apps.rb # FactoryBot factory
| Gem | Purpose |
|---|---|
blueprinter |
Serializer — explicit API response contract |
rspec-rails |
Test framework |
factory_bot_rails |
Test data factories |
shoulda-matchers |
Concise validation matchers |
See NOTES.md for a full account of architectural decisions, trade-offs, and what would be added with more time.