[Home](../README.md) / [Getting Started](README.md) / Your First Project

# Your First Project with TripWire

A step-by-step tutorial showing you how to build a real application with TripWire.

---

## What We'll Build

A simple web API that:
- Connects to a PostgreSQL database
- Uses an external API (OpenAI)
- Has configurable debug mode
- Validates all configuration at startup

**Time:** 15 minutes

---

## Prerequisites

- Python 3.11+ installed
- Basic Python knowledge
- (Optional) PostgreSQL running locally

---

## Step 1: Create Project Directory

```bash
mkdir my-tripwire-app
cd my-tripwire-app

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install TripWire
pip install tripwire-py
```

---

## Step 2: Initialize TripWire

```bash
tripwire init --project-type web
```

Output:
```
Welcome to TripWire! 🎯

Project type: web

✅ Created .env with web application template
✅ Created .env.example
✅ Updated .gitignore

Setup complete! ✅
```

This creates a `.env` file with starter variables for web apps:

```bash
# .env
DATABASE_URL=postgresql://localhost:5432/myapp
SECRET_KEY=<auto-generated-secure-key>
DEBUG=true
PORT=8000
```

---

## Step 3: Create Configuration Module

Create `config.py` to define your environment variables:

```python
# config.py
"""
Application configuration using TripWire.

All environment variables are validated at import time.
The app won't start if any required variable is missing or invalid.
"""

from tripwire import env

# Database Configuration
# - Must be a valid PostgreSQL URL
# - Marked as secret (won't appear in logs)
DATABASE_URL: str = env.require(
    "DATABASE_URL",
    format="postgresql",
    secret=True,
    description="PostgreSQL connection string"
)

# Security
# - Must be at least 32 characters
# - Marked as secret
SECRET_KEY: str = env.require(
    "SECRET_KEY",
    secret=True,
    min_length=32,
    description="Secret key for session signing"
)

# API Keys
# - Required for OpenAI API
# - Must match pattern (starts with 'sk-')
OPENAI_API_KEY: str = env.require(
    "OPENAI_API_KEY",
    pattern=r"^sk-[a-zA-Z0-9]{32,}$",
    secret=True,
    description="OpenAI API key"
)

# Application Settings
# - Optional, defaults to False
# - Type inferred from annotation (bool)
DEBUG: bool = env.optional(
    "DEBUG",
    default=False,
    description="Enable debug mode"
)

# - Optional, defaults to 8000
# - Must be valid port number
PORT: int = env.optional(
    "PORT",
    default=8000,
    min_val=1024,
    max_val=65535,
    description="HTTP server port"
)

# - Optional, defaults to INFO
# - Must be one of the allowed values
LOG_LEVEL: str = env.optional(
    "LOG_LEVEL",
    default="INFO",
    choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
    description="Logging level"
)

# Print configuration (secrets are masked)
if __name__ == "__main__":
    print("Configuration loaded successfully!")
    print(f"Database: {DATABASE_URL[:20]}... (masked)")
    print(f"Debug mode: {DEBUG}")
    print(f"Port: {PORT}")
    print(f"Log level: {LOG_LEVEL}")
```

---

## Step 4: Add Your API Key

Edit `.env` and add your OpenAI API key:

```bash
# .env
DATABASE_URL=postgresql://localhost:5432/myapp
SECRET_KEY=<auto-generated-secure-key>
OPENAI_API_KEY=sk-1234567890abcdef1234567890abcdef12345678
DEBUG=true
PORT=8000
LOG_LEVEL=DEBUG
```

---

## Step 5: Test Configuration Loading

```bash
python config.py
```

Expected output:
```
Configuration loaded successfully!
Database: postgresql://localho... (masked)
Debug mode: True
Port: 8000
Log level: DEBUG
```

---

## Step 6: Create Simple Application

Create `app.py`:

```python
# app.py
"""
Simple web application using TripWire configuration.
"""

from config import DATABASE_URL, SECRET_KEY, OPENAI_API_KEY, DEBUG, PORT, LOG_LEVEL
import logging

# Set up logging
logging.basicConfig(
    level=getattr(logging, LOG_LEVEL),
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

def main():
    """Main application entry point."""
    logger.info("Starting application...")
    logger.debug(f"Debug mode: {DEBUG}")
    logger.info(f"Server will run on port {PORT}")

    # Simulate database connection
    logger.info(f"Connecting to database: {DATABASE_URL[:30]}...")

    # Simulate API setup
    logger.info("OpenAI API key configured")

    # Your application logic here
    logger.info("Application ready!")

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logger.error(f"Application failed to start: {e}")
        raise
```

Run it:

```bash
python app.py
```

Output:
```
2025-10-09 10:00:00 - __main__ - INFO - Starting application...
2025-10-09 10:00:00 - __main__ - DEBUG - Debug mode: True
2025-10-09 10:00:00 - __main__ - INFO - Server will run on port 8000
2025-10-09 10:00:00 - __main__ - INFO - Connecting to database: postgresql://localhost:5432/...
2025-10-09 10:00:00 - __main__ - INFO - OpenAI API key configured
2025-10-09 10:00:00 - __main__ - INFO - Application ready!
```

---

## Step 7: Test Validation (Break Something!)

Let's see what happens when configuration is invalid.

### Test 1: Missing Required Variable

Remove `OPENAI_API_KEY` from `.env`:

```bash
python app.py
```

Error:
```
EnvironmentError: OPENAI_API_KEY is required but not set

Description: OpenAI API key

Suggestions:
  1. Add OPENAI_API_KEY to your .env file
  2. Set it as a system environment variable: export OPENAI_API_KEY=<value>
  3. Check .env.example for expected format
```

The app **refuses to start**. This prevents runtime crashes!

### Test 2: Invalid Format

Edit `.env` with an invalid API key:

```bash
OPENAI_API_KEY=invalid-key
```

Run again:

```bash
python app.py
```

Error:
```
EnvironmentError: OPENAI_API_KEY validation failed

Value: invalid-key
Validation: Pattern ^sk-[a-zA-Z0-9]{32,}$

The value does not match the required pattern.
```

### Test 3: Invalid Port

Edit `.env` with an invalid port:

```bash
PORT=99999
```

Run again:

```bash
python app.py
```

Error:
```
EnvironmentError: PORT validation failed

Value: 99999
Validation: Must be between 1024 and 65535

The value 99999 is outside the allowed range.
```

---

## Step 8: Generate .env.example

Now that your configuration is defined, generate `.env.example` automatically:

```bash
tripwire generate
```

Output:
```
Scanning Python files for environment variables...
Found 6 unique environment variable(s)
✓ Generated .env.example with 6 variable(s)
  - 3 required
  - 3 optional
```

Check `.env.example`:

```bash
cat .env.example
```

Output:
```bash
# OpenAI API key
OPENAI_API_KEY=

# PostgreSQL connection string
DATABASE_URL=

# Secret key for session signing
SECRET_KEY=

# Enable debug mode (optional, default: False)
DEBUG=False

# Logging level (optional, default: INFO)
LOG_LEVEL=INFO

# HTTP server port (optional, default: 8000)
PORT=8000
```

Beautiful! Your team members can now use this template.

---

## Step 9: Check for Team Sync

Simulate a team member missing a new variable. Add a new requirement to `config.py`:

```python
# Add to config.py
REDIS_URL: str = env.optional(
    "REDIS_URL",
    default="redis://localhost:6379",
    format="url",
    description="Redis cache URL"
)
```

Regenerate `.env.example`:

```bash
tripwire generate --force
```

Now check if your local `.env` has all required variables:

```bash
tripwire check
```

Output:
```
Comparing .env against .env.example

Missing Variables
┌─────────────┬───────────────────┐
│ Variable    │ Status            │
├─────────────┼───────────────────┤
│ REDIS_URL   │ Not set in .env   │
└─────────────┴───────────────────┘

Found 1 missing and 0 extra variable(s)

To add missing variables:
  tripwire sync
```

Sync your `.env`:

```bash
tripwire sync
```

Output:
```
Synchronizing .env with .env.example

Will add 1 missing variable(s):
  + REDIS_URL

✓ Synchronized .env
  Added 1 variable(s)

Note: Fill in values for new variables in .env
```

---

## Step 10: Scan for Secrets (Security Check)

Check if you've accidentally committed any secrets:

```bash
tripwire scan
```

Output:
```
Scanning for secrets...

Scanning .env file...
⚠️  Found 3 potential secret(s) in .env

┌───────────────────┬─────────────────┬──────────┐
│ Variable          │ Type            │ Severity │
├───────────────────┼─────────────────┼──────────┤
│ OPENAI_API_KEY    │ Generic API Key │ CRITICAL │
│ SECRET_KEY        │ Secret Key      │ CRITICAL │
│ DATABASE_URL      │ Database Creds  │ HIGH     │
└───────────────────┴─────────────────┴──────────┘

✓ .env is in .gitignore (safe)
```

Good - the secrets are detected but `.env` is gitignored!

---

## Congratulations!

You've built your first TripWire application! You now know how to:

- ✅ Initialize a project with `tripwire init`
- ✅ Define configuration with validation
- ✅ Use type inference for cleaner code
- ✅ Handle required vs optional variables
- ✅ Validate formats and constraints
- ✅ Generate `.env.example` automatically
- ✅ Keep team `.env` files in sync
- ✅ Scan for security issues

---

## What's Next?

### Essential Skills
- **[CLI Reference](../guides/cli-reference.md)** - Master all TripWire commands
- **[Type System](../advanced/type-system.md)** - Deep dive into type inference
- **[Validators](../reference/validators.md)** - All built-in validators

### Production Ready
- **[Configuration as Code](../guides/configuration-as-code.md)** - Use `.tripwire.toml` schemas
- **[Multi-Environment](../guides/multi-environment.md)** - Manage dev/staging/prod
- **[CI/CD Integration](../guides/ci-cd-integration.md)** - Automate validation

### Advanced Features
- **[Custom Validators](../advanced/custom-validators.md)** - Write your own
- **[Git Audit](../advanced/git-audit.md)** - Find secret leaks in history
- **[Framework Integration](../guides/framework-integration.md)** - FastAPI, Django, Flask

---

## Common Mistakes and Fixes

### Mistake 1: Importing Before Loading

```python
# ❌ Wrong - env.require() at function level won't fail fast
def get_config():
    DATABASE_URL = env.require("DATABASE_URL")
    return DATABASE_URL

# ✅ Right - env.require() at module level fails at import
DATABASE_URL: str = env.require("DATABASE_URL")
```

### Mistake 2: Not Using Type Annotations

```python
# ❌ Missing type hints
PORT = env.require("PORT", type=int, min_val=1024)

# ✅ Better - type inferred from annotation
PORT: int = env.require("PORT", min_val=1024)
```

### Mistake 3: Forgetting to Mark Secrets

```python
# ❌ Secret will appear in logs
API_KEY = env.require("API_KEY")

# ✅ Marked as secret (masked in output)
API_KEY: str = env.require("API_KEY", secret=True)
```

---

**[Back to Getting Started](README.md)**
