Building a Production App with Aider: A Practical Guide
Priya Patel
Product manager at an AI startup. Explores how agents reshape workflows.
Aider is one of those tools that sounds like a gimmick until you use it on a real project for a week. Then it changes how you think about writing code....
Building Production Software with Aider: A Practical Guide from the Terminal
Aider is one of those tools that sounds like a gimmick until you use it on a real project for a week. Then it changes how you think about writing code.
Unlike IDE-integrated copilots that sprinkle autocomplete suggestions, Aider operates as a full conversation partner in your terminal. It reads your files, understands your repo structure, makes multi-file edits, runs your tests, and commits changes with meaningful messages — all from a single prompt. It's closer to pairing with a senior developer than getting autocomplete from a junior one.
This guide walks through using Aider to build and maintain a real production application. Not a todo app. Not a demo. Real code that ships.
What Aider Actually Does (and Doesn't)
Before diving in, let's set expectations:
Aider does:
- Edit multiple files in a single conversation turn
- Automatically commit changes with descriptive messages
- Work with any language or framework
- Respect your
.gitignoreand project conventions - Run shell commands and react to their output
- Work with GPT-4, Claude 3.5 Sonnet, Gemini, and local models via Ollama
Aider doesn't:
- Replace understanding of your codebase
- Make architectural decisions well without guidance
- Handle ambiguous requirements without clarification
- Always get things right on the first try (but it's remarkably good at fixing its own mistakes)
The key insight: Aider is a force multiplier for developers who already know what they want to build. It's not a replacement for thinking — it's a replacement for typing.
Installation and Setup
Basic Installation
# Install via pip (recommended)
pip install aider-chat
# Or via pipx for isolation
pipx install aider-chat
# Verify installation
aider --version
API Key Configuration
Aider needs an LLM backend. Set your API keys as environment variables:
# For OpenAI (GPT-4o is the default and excellent)
export OPENAI_API_KEY="sk-..."
# For Anthropic (Claude 3.5 Sonnet is excellent for code)
export ANTHROPIC_API_KEY="sk-ant-..."
# For Google Gemini
export GEMINI_API_KEY="..."
You can also put these in ~/.aider.conf.yml:
# ~/.aider.conf.yml
model: claude-3-5-sonnet-20241022
auto-commits: true
dark-mode: true
Per-Project Configuration
Create a .aider.conf.yml in your project root for project-specific settings:
# .aider.conf.yml
model: gpt-4o
auto-commits: true
dirty-commits: false
no-git: false
lint: true
test-cmd: "pytest tests/ -x -q"
The .aiderignore File
Create .aiderignore in your repo root, similar to .gitignore. This prevents Aider from reading files that would waste context or contain sensitive data:
# .aiderignore
*.pyc
__pycache__/
.env
.env.*
node_modules/
dist/
build/
*.min.js
*.min.css
vendor/
.mypy_cache/
.pytest_cache/
coverage/
*.log
This matters more than people realize. Aider's context window is finite. Every irrelevant file it reads is a file that competes with your actual code for attention.
Starting a Session
The Basics
# Start Aider in your project directory
cd /path/to/your/project
# Start with specific files
aider src/models/user.py src/api/routes.py
# Start with all Python files
aider --files "*.py"
# Start with a specific model
aider --model claude-3-5-sonnet-20241022
# Start in "architect" mode (planning before editing)
aider --model claude-3-5-sonnet-20241022 --editor-model gpt-4o
The --architect flag is worth highlighting. It uses one model to plan changes and another to implement them. In practice, Claude for planning + GPT-4o for implementation is a strong combination. Claude tends to produce better architectural reasoning; GPT-4o is excellent at precise code generation.
Understanding Aider's Modes
Aider operates in several input modes during a session:
| Mode | Prefix | What Happens |
|---|---|---|
| Code edit | (none) | Aider modifies files based on your request |
| Ask | /ask |
Aider answers questions without editing code |
| Architect | /architect |
Aider plans changes, then edits |
| Shell | /run or ! |
Execute shell commands |
| Command | / |
Aider commands (add files, switch model, etc.) |
# Add a file to the chat context
/add src/config.py
# Drop a file from context
/drop src/old_module.py
# Ask a question without editing
/ask How does the authentication middleware work?
# Run a test suite
/run pytest tests/test_auth.py -v
# See what files are in context
/ls
# Undo the last change
/undo
# Switch models mid-session
/model claude-3-5-sonnet-20241022
A Real-World Workflow: Building an API Service
Let me walk through how I actually use Aider on a production project. I'll use a concrete example: building a FastAPI service with PostgreSQL, authentication, and proper test coverage.
Step 1: Project Bootstrap
Start by adding your main configuration and entry point files:
aider pyproject.toml src/main.py src/config.py
Then give Aider your first prompt:
I'm building a FastAPI service with PostgreSQL using SQLAlchemy 2.0 (async),
Pydantic v2, and Alembic for migrations. Set up the project structure with:
1. pyproject.toml with all dependencies
2. src/config.py with Pydantic Settings for env-based configuration
3. src/main.py with FastAPI app setup, CORS, and health check endpoint
4. src/database.py with async SQLAlchemy engine and session factory
Use async/await throughout. Include proper type hints.
Aider will create all four files, commit them, and present the changes. This is where the magic starts — instead of copy-pasting boilerplate from a tutorial, you get a coherent, interconnected set of files in one shot.
Step 2: Iterative Development with Context Management
Here's where most people get Aider wrong. They dump their entire codebase into context and wonder why responses degrade. Context management is the single most important skill with Aider.
The right approach:
# Work on the user model
aider src/models/base.py src/models/user.py src/schemas/user.py
# Switch to authentication
aider src/api/auth.py src/services/auth_service.py src/models/user.py
# Work on tests
aider tests/test_auth.py src/api/auth.py src/services/auth_service.py
Notice I'm adding src/models/user.py to the auth session because auth needs to understand the user model. But I'm not adding src/config.py unless the auth code needs config changes. Every file in context should earn its place.
Step 3: Multi-File Edits in Practice
One of Aider's strongest capabilities is coordinated multi-file edits. Here's a real prompt I used recently:
Add a rate limiting middleware to the application. Requirements:
1. Use a sliding window algorithm with Redis as the backend
2. Create src/middleware/rate_limit.py with the middleware class
3. Add RATE_LIMIT_REQUESTS and RATE_LIMIT_WINDOW_SECONDS to src/config.py
4. Register the middleware in src/main.py
5. Add redis to pyproject.toml dependencies
6. Create tests/test_rate_limit.py with unit tests using fakeredis
The middleware should return a 429 status with a Retry-After header.
Use dependency injection for the Redis client so tests can mock it.
Aider touched five files in a single turn. The config changes were consistent with the existing pattern. The middleware was wired into the app correctly. The tests actually mocked at the right abstraction layer. This would have taken me 20-30 minutes manually — it took about 90 seconds.
Step 4: Git Integration Done Right
Aider auto-commits changes with descriptive messages. This is surprisingly useful:
# Aider's auto-generated commit messages are typically good:
# "Add rate limiting middleware with Redis backend and sliding window algorithm"
# You can configure commit behavior
aider --auto-commits # Enable auto-commits (default)
aider --no-auto-commits # Disable auto-commits
aider --dirty-commits # Commit even with dirty working tree
My workflow with git:
- Let Aider auto-commit as I work
- Review the commit log periodically with
git log --oneline -20 - Use
git rebase -ito squash related Aider commits before pushing - Push clean, logical commit groups to the remote
# After a session, squash Aider's granular commits
git rebase -i main
# Pick the first commit, squash the rest
# Edit the combined commit message to be a clean description
Important: Aider respects your git state. If you have uncommitted changes when you start, it will warn you. Commit or stash before starting a session.
Step 5: Test-Driven Development with Aider
This is where Aider becomes genuinely powerful for production code. The TDD workflow:
Write the test first (yourself or with Aider):
aider tests/test_user_service.py
Write a test file for a UserService class that doesn't exist yet.
The service will be at src/services/user_service.py.
Test cases:
1. create_user with valid data returns UserResponse
2. create_user with duplicate email raises ConflictError
3. get_user_by_id with valid ID returns UserResponse
4. get_user_by_id with invalid ID raises NotFoundError
5. update_user with partial data merges correctly
6. delete_user soft-deletes (sets deleted_at timestamp)
Use pytest, pytest-asyncio, and factory patterns for test data.
Mock the database session. Follow the project's existing test patterns
in tests/test_auth.py.
Now implement to make the tests pass:
aider src/services/user_service.py src/schemas/user.py src/models/user.py
Implement the UserService class to make all tests in
tests/test_user_service.py pass. Refer to the test file for the
expected interface. Follow the patterns in src/services/auth_service.py.
Run the tests:
/run pytest tests/test_user_service.py -v
If tests fail, Aider sees the output and fixes the code:
The tests are failing. Fix the implementation to make them pass.
This loop — test, implement, run, fix — is where Aider shines. It's exceptionally good at reading test output and correcting its own code.
Step 6: Using /ask for Understanding Before Changing
Before making changes to unfamiliar code, use /ask mode:
/ask Walk me through the authentication flow. What happens from
login request to JWT token generation? Trace through the code
in src/api/auth.py, src/services/auth_service.py, and
src/models/user.py.
This gives you a mental model before you start changing things. It's also useful for code review:
/ask Review the error handling in src/services/payment_service.py.
Are there any cases where exceptions could leak sensitive information?
Are all database sessions properly closed?
Advanced Techniques
The /architect Mode
For complex changes, architect mode plans before executing:
/architect Refactor the notification system to support multiple channels
(email, SMS, push). Currently everything is in src/services/notification.py
and it only supports email.
Design a strategy pattern with:
- A NotificationChannel abstract base class
- Concrete implementations for each channel
- A NotificationService that dispatches to the right channel
- Configuration-driven channel selection
Plan the file structure and interfaces first, then implement.
Aider will outline the plan, show you the proposed changes, and then implement them after you approve.
Selective File Editing with /add and /drop
Manage context actively during a session:
# Start focused
/add src/models/user.py
# Expand when needed
/add src/services/auth_service.py src/api/auth.py
# Contract when switching focus
/drop src/models/user.py
/add src/services/payment_service.py
# See what's in context
/ls
Running Linters and Formatters
Configure Aider to lint after edits:
aider --lint-cmd "python: ruff check --fix" --lint-cmd "python: ruff format"
Or in .aider.conf.yml:
lint: true
lint-cmd:
- "python: ruff check --fix"
- "python: ruff format"
Aider will automatically run these after editing Python files and fix any issues it introduced.
Working with Large Codebases
For large projects, use a "map" file. Aider can generate a repository map that gives the LLM a structural overview without reading every file:
# Aider does this automatically, but you can influence it
# by ensuring your code has good docstrings and type hints
# (which help the map be more informative)
In practice, for projects over ~50 files, I recommend:
- Always specify files explicitly — don't rely on Aider guessing
- Use
.aiderignoreaggressively — exclude generated code, migrations, vendor code - Keep sessions focused — one feature or bug per session
- Use
/dropliberally — remove files from context when you're done with them
Using Local Models with Ollama
For sensitive codebases where you can't send code to external APIs:
# Start Ollama with a code-focused model
ollama pull codestral
ollama pull deepseek-coder-v2
# Use with Aider
aider --model ollama/codestral
aider --model ollama/deepseek-coder-v2
Honest assessment: local models are meaningfully worse than GPT-4o or Claude 3.5 Sonnet for complex, multi-file edits. They work well for single-file changes, simple refactors, and writing tests. For architectural changes across many files, you'll notice the quality gap. That said, DeepSeek Coder V2 and Codestral are surprisingly capable for their size.
Tips That Actually Matter
1. Be Specific About Constraints
Bad prompt:
Add caching to the API
Good prompt:
Add Redis-based caching to the GET /users/{id} endpoint in src/api/users.py.
Cache for 5 minutes. Use a cache-aside pattern. Invalidate on PUT/DELETE.
Add a cache key that includes the user ID. Handle Redis connection errors
gracefully — if Redis is down, the endpoint should still work (just uncached).
The second prompt eliminates ambiguity. Aider won't guess at caching strategies, TTLs, or error handling — it'll implement exactly what you described.
2. Reference Existing Patterns
Follow the same error handling pattern used in src/api/orders.py.
Use the same response schema structure as src/schemas/order.py.
Write tests following the patterns in tests/test_orders.py.
Aider is excellent at pattern matching. If your codebase has consistent conventions, it will follow them — but only if you point them out.
3. Use /run to Create Feedback Loops
# Run tests and let Aider see the output
/run pytest tests/ -x -v
# Run the linter
/run ruff check src/
# Run the type checker
/run mypy src/ --ignore-missing-imports
# Run the application
/run uvicorn src.main:app --reload
When tests fail, Aider reads the traceback and fixes the code. This is its most reliable workflow. Feed it concrete error output, not vague descriptions of what's wrong.
4. Don't Fight the Context Window
If Aider starts making mistakes or forgetting things from earlier in the conversation, the context is probably too full. Solutions:
/dropfiles you're no longer working with- Start a new session for a different feature
- Use
/clearto reset the conversation while keeping files in context
5. Review Everything
This should go without saying, but: read every change Aider makes before committing to your workflow. Aider is very good, but it occasionally:
- Introduces subtle bugs in edge cases
- Over-engineers simple solutions
- Adds unnecessary dependencies
- Misses error handling paths
- Creates security issues (especially around input validation)
I've caught Aider generating SQL queries that were technically correct but vulnerable to injection in edge cases. I've seen it add eval() calls where a dictionary lookup would suffice. Trust but verify.
6. Use Aider for Code Review
An underrated use case:
/ask Review the changes in src/services/payment_service.py for:
1. Race conditions in concurrent requests
2. Proper error handling and rollback
3. SQL injection vulnerabilities
4. Missing input validation
5. Compliance with PCI-DSS requirements for handling card data
Be specific about line numbers and exact issues.
A Real Session Transcript
Here's an abbreviated version of an actual session I had building a webhook delivery system:
$ aider src/models/webhook.py src/schemas/webhook.py
> Create a Webhook model and Pydantic schemas. The webhook has:
> - id (UUID), url (validated HTTPS URL), secret (auto-generated),
> - events (list of event types it subscribes to),
> - active (bool, default True), created_at, updated_at
> - last_triggered_at (nullable)
> Create schemas for Create, Update, and Response.
[Creates both files, commits: "Add webhook model and schemas"]
$ /add src/services/webhook_service.py
> Create a WebhookService with methods:
> - register_webhook(data) -> validates URL is reachable (HEAD request),
> generates secret, stores webhook
> - get_webhooks_for_event(event_type) -> returns all active webhooks
> subscribed to this event
> - deliver_payload(webhook_id, payload) -> signs payload with HMAC-SHA256
> using webhook secret, sends POST with X-Webhook-Signature header,
> retries up to 3 times with exponential backoff
> - record_delivery(webhook_id, status_code, response_time_ms)
[Creates service, commits: "Add webhook service with delivery and HMAC signing"]
$ /run pytest tests/test_webhook_service.py -v
> [Tests fail: test_delivery_signature_mismatch - the HMAC comparison
> is using == instead of hmac.compare_digest]
Fix the timing attack vulnerability in the signature comparison.
[Fixes the code, commits: "Use hmac.compare_digest for constant-time signature comparison"]
That last exchange is telling. Aider introduced a subtle security issue (timing attack via == comparison), the test suite caught it, and Aider fixed it correctly. The TDD loop worked exactly as designed.
Limitations and Honest Assessment
After using Aider extensively on production code, here's where it falls short:
Architectural decisions: Aider implements well but doesn't design well. If you ask it to "design a scalable architecture," you'll get something generic. Tell it what architecture to use, and it'll implement it excellently.
Very large refactors: Refactoring across 20+ files is unreliable. Break it into smaller, verifiable chunks.
Ambiguous requirements: "Make it better" produces unpredictable results. "Reduce the response time of this endpoint by adding database query optimization with eager loading" produces excellent results.
Context-dependent bugs: Bugs that require understanding runtime state, race conditions, or complex interactions across distant parts of the codebase are hard for Aider to diagnose from code alone. It needs error output to react to.
Cost: Running GPT-4o or Claude 3.5 Sonnet on large contexts is not cheap. A heavy session can cost $5-15. Budget accordingly and manage context carefully.
The Bottom Line
Aider is the most productive development tool I've adopted in years. Not because it writes perfect code — it doesn't. But because it compresses the implementation cycle dramatically. The gap between "I know what I want to build" and "the code exists and works" has shrunk by 60-70% on the kinds of tasks where it excels: CRUD operations, API endpoints, test writing, configuration, boilerplate, and well-defined refactors.
The developers who get the most from Aider are those who already have strong engineering judgment. They know what good code looks like, they can spot when Aider goes off-track, and they use it as a highly capable implementation partner rather than an oracle.
Install it. Try it on your next feature branch. Keep the session focused, the prompts specific, and your review hat on. You'll know within an hour whether it fits your workflow.