Back to Home
Coding Agents

Building a Production App with Aider: A Practical Guide

Priya Patel

Product manager at an AI startup. Explores how agents reshape workflows.

March 13, 202615 min read

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 .gitignore and 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:

  1. Let Aider auto-commit as I work
  2. Review the commit log periodically with git log --oneline -20
  3. Use git rebase -i to squash related Aider commits before pushing
  4. 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:

  1. Always specify files explicitly — don't rely on Aider guessing
  2. Use .aiderignore aggressively — exclude generated code, migrations, vendor code
  3. Keep sessions focused — one feature or bug per session
  4. Use /drop liberally — 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:

  • /drop files you're no longer working with
  • Start a new session for a different feature
  • Use /clear to 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.

Keywords

AI agentcoding-agents