Skip to content

Development Workflow

Last Updated: 2025-11-22

Overview

This guide outlines the development workflow for the AccessALI Customer Portal, from setting up your environment to deploying to production. Following this workflow ensures consistency, quality, and efficient collaboration across the development team.

Container-First Development

AccessALI uses a container-first approach with Docker as the primary development environment. This ensures consistency across all team members and production parity.

Daily Development Workflow

1. Starting Your Day

# Navigate to source directory
cd src

# Pull latest changes
git pull origin develop

# Start all services (if not already running)
pnpm docker:dev

# Check service status
pnpm docker:ps

Services Running

You should see 4 containers running:

  • app (Next.js on port 3000)
  • postgres (PostgreSQL on port 5432)
  • redis (Redis on port 6379)
  • prisma-studio (Prisma Studio on port 5555)

2. Making Changes

Feature Development Flow

graph LR
    A[Create Branch] --> B[Develop Feature]
    B --> C[Test Locally]
    C --> D[Type Check]
    D --> E[Lint Code]
    E --> F[Commit Changes]
    F --> G[Push to GitHub]
    G --> H[Create PR]
    H --> I[Code Review]
    I --> J[Merge to Develop]

Step-by-Step Process

1. Create Feature Branch

# Create and switch to new branch
git checkout -b feature/SP-123-user-dashboard

# Branch naming convention:
# feature/SP-XXX-description  (new features)
# fix/SP-XXX-description      (bug fixes)
# chore/SP-XXX-description    (maintenance)
# docs/SP-XXX-description     (documentation)

2. Develop Feature

Follow the layered architecture pattern:

// 1. Define database schema (if needed)
// File: prisma/schema.prisma
model Feature {
  id String @id @default(cuid())
  // ... fields
}

// 2. Run migration
// Terminal: pnpm docker:db:migrate

// 3. Create repository
// File: src/lib/repositories/feature-repository.ts
export async function createFeature(data: FeatureInput) {
  return prisma.feature.create({ data })
}

// 4. Create use case
// File: src/lib/use-cases/feature.ts
export async function createFeatureUseCase(userId: string, data: FeatureInput) {
  // Business logic here
  return createFeature(data)
}

// 5. Create server action
// File: src/app/actions/feature.ts
'use server'
export async function createFeatureAction(data: unknown) {
  const session = await auth()
  if (!session) throw new Error('Unauthorized')

  const validated = featureSchema.parse(data)
  return createFeatureUseCase(session.user.id, validated)
}

// 6. Create page
// File: src/app/feature/page.tsx
export default async function FeaturePage() {
  const data = await getFeatureData()
  return <FeatureView data={data} />
}

3. Test Locally

# Check logs
pnpm docker:logs:app

# Access application
open http://localhost:3000

# Check database
pnpm docker:db:studio
# Opens http://localhost:5555

4. Type Check

cd src
pnpm type-check

5. Lint Code

cd src
pnpm lint

# Fix auto-fixable issues
pnpm lint:fix

6. Commit Changes

# Stage files
git add .

# Commit with conventional commit message
git commit -m "feat(dashboard): add user statistics widget

- Add statistics component
- Implement data fetching
- Add responsive layout

Closes SP-123"

Commit Message Format:

<type>(<scope>): <subject>

<body>

<footer>

Types: feat, fix, docs, style, refactor, test, chore

Examples:

feat(auth): implement OAuth login
fix(payments): resolve payment calculation error
docs(readme): update installation instructions
chore(deps): upgrade Next.js to 15.5.6

7. Push to GitHub

# Push branch
git push origin feature/SP-123-user-dashboard

# If branch already exists
git push

8. Create Pull Request

Use GitHub UI or CLI:

# Using GitHub CLI
gh pr create \
  --title "feat(dashboard): Add user statistics widget" \
  --body "## Summary
- Implements user statistics widget
- Fetches data from use case layer
- Responsive design with shadcn/ui

## Testing
- [ ] Verified on desktop (Chrome, Safari)
- [ ] Verified on mobile
- [ ] Unit tests passing
- [ ] Type check passing

Closes #123"

3. Code Review Process

PR Review Checklist

Reviewer Checklist:

  • Code Quality
  • Follows layered architecture
  • No code duplication
  • Clear variable/function names
  • Appropriate comments where needed

  • Functionality

  • Feature works as described
  • No regressions
  • Edge cases handled
  • Error handling implemented

  • Security

  • Input validation with Zod
  • Authorization checks in place
  • No sensitive data exposed
  • SQL injection prevented

  • Performance

  • Efficient database queries
  • Proper indexing
  • No N+1 queries
  • Appropriate caching

  • Testing

  • Type check passes
  • Lint passes
  • Manual testing completed

Requesting Changes

### Feedback

**Architecture:**
- Move business logic from server action to use case layer
- Create dedicated repository method instead of inline Prisma query

**Security:**
- Add Zod validation for user input
- Implement authorization check before data access

**Code Quality:**
- Extract magic numbers to constants
- Add JSDoc comments for complex logic

**Performance:**
- Use database transaction for multiple operations
- Add index on `userId` column

4. Merging

Once approved:

# Update branch with latest develop
git checkout develop
git pull origin develop
git checkout feature/SP-123-user-dashboard
git merge develop

# Resolve conflicts if any
# Then push
git push

# Merge via GitHub UI
# Choose "Squash and merge" for clean history

Database Changes

Adding New Models

1. Update Schema

// prisma/schema.prisma
model NewModel {
  id        String   @id @default(cuid())
  field1    String
  field2    Int?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([field1])
  @@map("new_models")
}

2. Create Migration

cd src
pnpm docker:db:migrate
# Prompts for migration name: "add_new_model"

3. Verify Migration

# Check generated SQL
cat prisma/migrations/*/migration.sql

# Test migration
pnpm docker:db:studio

4. Commit Migration

git add prisma/
git commit -m "feat(db): add NewModel schema"

Migration Best Practices

  • Always review generated SQL before committing
  • Test migrations in development first
  • Never edit existing migration files
  • Use prisma migrate deploy in production

Modifying Existing Models

Safe Changes: - Adding optional fields - Adding indexes - Renaming fields (with @map)

Breaking Changes: - Removing fields (requires data migration) - Changing field types - Making fields required

Data Migration Example:

// Step 1: Add new optional field
model User {
  newField String?
}

// Step 2: Run migration
// Step 3: Backfill data
// Step 4: Make field required in new migration
model User {
  newField String
}

Testing Strategy

Manual Testing

# 1. Start environment
pnpm docker:dev

# 2. Test feature in browser
open http://localhost:3000

# 3. Check different scenarios
# - Happy path
# - Error cases
# - Edge cases
# - Different user roles

# 4. Test on different devices
# - Desktop (Chrome, Safari, Firefox)
# - Mobile (iOS Safari, Chrome)
# - Tablet

Type Checking

cd src
pnpm type-check

# Watch mode
pnpm type-check --watch

Linting

cd src

# Check for issues
pnpm lint

# Auto-fix
pnpm lint:fix

Unit Testing (Planned)

cd src

# Run all tests
pnpm test

# Watch mode
pnpm test:watch

# Coverage
pnpm test:coverage

Environment Management

Environment Files

.env.docker        # Docker development (committed)
.env.local         # Local development (ignored)
.env.production    # Production (Vercel)

Adding Environment Variables

1. Add to .env.docker

# .env.docker
NEW_VARIABLE="development-value"

2. Add to Vercel

# Using Vercel CLI
vercel env add NEW_VARIABLE production

# Or via Vercel Dashboard
# Project Settings → Environment Variables

3. Update Documentation

Update relevant docs with new variable and its purpose.

Common Tasks

Viewing Logs

# All services
pnpm docker:logs

# Specific service
pnpm docker:logs:app
pnpm docker:logs:db

# Follow logs
pnpm docker:logs:app -f

Database Operations

# Access Prisma Studio
pnpm docker:db:studio

# Run migrations
pnpm docker:db:migrate

# Seed database
pnpm docker:db:seed

# Reset database (destructive!)
pnpm docker:db:reset

Container Management

# Start services
pnpm docker:dev

# Stop services
pnpm docker:down

# Restart service
pnpm docker:restart:app

# View running containers
pnpm docker:ps

# Access container shell
pnpm docker:shell

# Clean everything (nuclear option)
pnpm docker:clean

Debugging

View Application Logs:

pnpm docker:logs:app

Access Container Shell:

pnpm docker:shell

# Inside container
ps aux              # View processes
cat .next/trace     # View Next.js traces

Database Debugging:

# Access database shell
pnpm docker:db:shell

# Inside PostgreSQL
\dt                 # List tables
\d users            # Describe users table
SELECT * FROM users LIMIT 5;

Troubleshooting

Port Already in Use

# Find process using port 3000
lsof -ti:3000

# Kill process
kill -9 $(lsof -ti:3000)

# Or change port in docker-compose.yml

Container Won't Start

# Check logs
pnpm docker:logs

# Rebuild containers
pnpm docker:clean
pnpm docker:dev:build

Database Connection Issues

# Check PostgreSQL is running
pnpm docker:ps

# Restart database
pnpm docker:restart:db

# Check connection string
cat .env.docker | grep DATABASE_URL

Hot Reload Not Working

# Restart app container
pnpm docker:restart:app

# Or rebuild
pnpm docker:down
pnpm docker:dev:build

Best Practices

Code Organization

DO: - Follow layered architecture - Use absolute imports (@/) - One component per file - Collocate related files - Keep files under 300 lines

DON'T: - Bypass architecture layers - Use relative imports (../../) - Create God files - Duplicate code - Commit commented code

Git Workflow

DO: - Pull before creating branch - Commit often with clear messages - Keep PRs focused and small - Resolve merge conflicts promptly - Delete branches after merge

DON'T: - Commit directly to develop/main - Create massive PRs - Force push to shared branches - Commit sensitive data - Leave stale branches

Performance

DO: - Use Server Components by default - Implement proper database indexes - Cache expensive operations - Optimize images with Next.js Image - Use dynamic imports for heavy components

DON'T: - Use Client Components unnecessarily - Make N+1 database queries - Fetch data in loops - Load all data upfront - Skip performance testing

Next Steps