Skip to content

Engineering Checklist

World-Class Engineering Checklist

A practical guide for building production-grade software. Use this when prompting AI or reviewing your own work.


🏗️ Architecture & Design

Before Writing Code

  • Define clear requirements - What problem are you solving? For whom?
  • Design the data model first - Sketch out database tables and relationships
  • Choose the right tool - Don't use a framework because it's trendy; use it because it fits
  • Plan for scale - Will this handle 10x users? 100x?
  • Identify failure points - What breaks if the database is slow? API is down?

System Design Principles

  • Separation of concerns - Each file/function should have ONE job
  • Don't repeat yourself (DRY) - Extract common patterns into reusable functions
  • KISS (Keep It Simple, Stupid) - The simplest solution that works is usually best
  • YAGNI (You Aren't Gonna Need It) - Don't build features you don't need yet

Example prompt for AI:

"Design a database schema for [feature]. Include relationships, indexes, and explain your choices. Consider how this scales to 100k users."


Security

The checks for a Next.js and Supabase app built with an agent. Each one links to the chapter that explains it. Run them before you invite real users, and again whenever the agent adds a route, a table, or a package.

Where code runs

  • No secret in browser code. Every NEXT_PUBLIC_ variable is public by design, and no key is typed into a "use client" file (Data and APIs).
  • Every check that protects data or money also runs on the server or in the database. Checks in the browser are for speed only (Anatomy of an Application).

The database

  • RLS is on for every table, with a policy for each action the app uses. Supabase's Security Advisor (in the dashboard under Advisors) lists every table without it as an error (Data and APIs).
  • The two-account drill passes. Signed in as user B, you cannot see, change, or delete user A's records through the screens, by changing ids, or with a direct query.
  • Private files are in private storage buckets, with policies.

Your endpoints

  • Every route and Server Action checks who is calling, whether the row is theirs, and how much they may use (Data and APIs). A route that uses the secret key checks ownership itself.
  • Paid calls have two limits: a per-user cap in your code and a monthly spend limit on the provider account.

Content and models

  • No text from a user or a model is shown as HTML or joined into SQL. No dangerouslySetInnerHTML or rehype-raw on that text, user-supplied links accept only http and https, and queries pass user text as a value (Data and APIs).
  • Model output decides no permission, price, or action by itself. Costly actions wait for a person or a rule in code, and each tool gets only the access its job needs (Models in Your Product).

Data

  • Logs, analytics events, and prompts carry ids, not personal data. Check what each server console.log prints (Data and APIs).
  • No real student or client data goes into an AI tool until the contract and the tool's data terms allow it. Build with made-up data.

Accounts, repo, and packages

  • Two-factor sign-in is on for GitHub, Vercel, Supabase, and your model provider's console. Those accounts hold every key the app uses.
  • GitHub push protection is on, and you never bypass it. It blocks a push that contains a recognized kind of key, and it is on by default for public repositories. If it blocks you, rotate the key.
  • Dependabot alerts are on (repo Settings, then Code security), so GitHub warns you when a package you use has a known vulnerability. npm audit gives the same list on your laptop.
  • Every package the agent installs is one you meant to install. Check the name on npmjs.com before installing: models sometimes suggest package names that do not exist, and attackers publish harmful packages under names like those. Review changes to package.json in every pull request.
  • A leaked key is rotated, not only deleted from the code (Data and APIs).
  • The agent cannot reach production by accident. Production keys stay out of .env.local, and the agent works against a development database (Data and APIs, Directing the Agent).

Example prompt for AI:

"Review this repo against the Security section of the Engineering Checklist. For each item, say pass or fail, and give the file and line that shows it. Do not change anything yet."


✅ Testing

Test Pyramid (Bottom to Top)

  1. Unit Tests (70%) - Test individual functions in isolation
  2. Integration Tests (20%) - Test how components work together
  3. E2E Tests (10%) - Test user flows through the entire app

What to Test

  • Happy path - Does it work when everything goes right?
  • Edge cases - Empty strings, null values, very large numbers
  • Error cases - What happens when the database is down? API fails?
  • Security - Can users access data they shouldn't?
  • Performance - Does it handle expected load?

Coverage Goals

  • Critical paths: 100% - Payment processing, auth, data loss prevention
  • Business logic: 80%+ - Core features
  • UI components: 60%+ - Important user interactions
  • Total coverage: 70%+ - Industry standard for production code

Example prompt for AI:

"Write comprehensive tests for this function. Include: happy path, edge cases (null, empty, large inputs), and error handling. Use Jest."


🐛 Error Handling

Never Fail Silently

  • Catch and handle errors - Every async operation can fail
  • Log errors properly - Use structured logging (JSON format)
  • User-friendly messages - "Something went wrong" is useless
  • Specific error codes - Help debugging (e.g., ERR_DB_CONNECTION_FAILED)
  • Retry logic - Transient failures (network issues) should retry with exponential backoff

Error Handling Strategy

try {
  await riskyOperation()
} catch (error) {
  // 1. Log the error with context
  logger.error('Failed to process payment', {
    userId,
    amount,
    error: error.message,
    stack: error.stack
  })

  // 2. Alert if critical
  if (isCritical) {
    alerting.notify(error)
  }

  // 3. Return user-friendly message
  return {
    error: 'Unable to process payment. Please try again.',
    code: 'PAYMENT_FAILED'
  }
}

Example prompt for AI:

"Add comprehensive error handling to this code. Log errors with context, return user-friendly messages, and implement retry logic for network failures."


📊 Performance

Frontend Performance

  • Lazy load components - Don't load everything at once
  • Optimize images - Compress, use WebP, lazy load
  • Code splitting - Bundle only what's needed per page
  • Minimize bundle size - Use bundle analyzer, remove unused deps
  • Cache aggressively - Browser cache, CDN, service workers

Backend Performance

  • Database indexes - Index columns used in WHERE, JOIN, ORDER BY
  • Avoid N+1 queries - Use JOIN or batch loading
  • Cache expensive operations - Redis for frequently accessed data
  • Use pagination - Never return unbounded lists
  • Optimize API calls - Batch requests, use GraphQL for flexible queries

Performance Budgets

  • First Contentful Paint: < 1.8s
  • Time to Interactive: < 3.8s
  • API Response Time: < 200ms (p95)
  • Database Queries: < 50ms (p95)

Example prompt for AI:

"Optimize this code for performance. Look for: N+1 queries, missing indexes, unnecessary API calls, and opportunities for caching."


📝 Code Quality

Clean Code Principles

  • Meaningful names - getUserById() not get()
  • Functions do one thing - If it needs "and" in the name, split it
  • Keep functions short - Aim for < 20 lines
  • Comment the "why", not the "what" - Code should be self-documenting
  • No magic numbers - Use named constants

Code Review Checklist

  • Does it work? - Test the code yourself
  • Is it readable? - Can someone else understand it in 6 months?
  • Is it maintainable? - Can it be easily modified?
  • Is it tested? - Are there tests for this change?
  • Is it secure? - Any vulnerabilities?
  • Is it performant? - Will it scale?

TypeScript Best Practices

  • Enable strict mode - Catch more errors at compile time
  • Avoid any - Use proper types or unknown
  • Use interfaces for objects - Better documentation and autocomplete
  • Type your API responses - Don't trust external data
  • Use enums for fixed values - Better than string literals

Example prompt for AI:

"Refactor this code for readability and maintainability. Use meaningful names, break into smaller functions, add type safety, and remove code duplication."


📚 Documentation

What to Document

  • README.md - What it does, how to run it, how to deploy
  • API documentation - All endpoints, parameters, responses (use OpenAPI/Swagger)
  • Code comments - Complex algorithms, business logic, "why" decisions
  • Architecture decisions - Why you chose this approach over alternatives
  • Setup instructions - Environment variables, database setup, external services

Good Documentation Format

/**
 * Calculates the user's fluency score based on word mastery.
 *
 * Algorithm: Weighted average of all four skills (listen, read, write, speak)
 * with higher weight given to productive skills (write, speak).
 *
 * @param userId - The user's unique identifier
 * @param options - Optional filtering (date range, CEFR level)
 * @returns Fluency score from 0-100
 * @throws {DatabaseError} If unable to fetch user data
 *
 * @example
 * const score = await calculateFluencyScore('user-123')
 * console.log(score) // 78.5
 */
async function calculateFluencyScore(
  userId: string,
  options?: FluencyOptions
): Promise<number>

Example prompt for AI:

"Add JSDoc comments to all functions explaining purpose, parameters, return values, and edge cases. Include examples where helpful."


🚀 DevOps & Deployment

CI/CD Pipeline

  • Automated tests on PR - All tests must pass before merge
  • Linting & formatting - Enforce code style automatically
  • Build verification - Does it compile?
  • Security scanning - Check for vulnerable dependencies
  • Automated deployment - Push to main = deploy to production

Monitoring & Observability

  • Error tracking - Sentry, Rollbar, or similar
  • Performance monitoring - New Relic, Datadog, or similar
  • Logging - Structured logs (JSON) sent to central system
  • Metrics - Track user actions, API latency, error rates
  • Alerts - Get notified when things break (Slack, PagerDuty)

Database Best Practices

  • Migrations, not manual changes - Track all schema changes in code
  • Backup strategy - Automated daily backups, test restore process
  • Separate environments - Dev, staging, production databases
  • Connection pooling - Reuse database connections efficiently
  • Query monitoring - Alert on slow queries

Example prompt for AI:

"Create a GitHub Actions workflow that: runs tests, checks linting, builds the app, and deploys to Vercel on merge to main."


🎯 User Experience

Perceived Performance

  • Optimistic updates - Update UI immediately, sync in background
  • Loading states - Show spinners, skeletons, progress bars
  • Error states - Clear messaging with recovery actions
  • Empty states - Helpful guidance when no data exists
  • Animations - Smooth transitions (but don't overdo it)

Accessibility (a11y)

  • Keyboard navigation - All features work without mouse
  • Screen reader support - Semantic HTML, ARIA labels
  • Color contrast - WCAG AA minimum (4.5:1 for text)
  • Focus indicators - Visible outlines on focused elements
  • Alt text for images - Describe what's in the image

Mobile Responsiveness

  • Touch targets - At least 44x44px for buttons
  • Viewport meta tag - Proper mobile scaling
  • Responsive layouts - Works on all screen sizes
  • Test on real devices - Not just browser dev tools

Example prompt for AI:

"Improve this component's UX: add loading/error states, make it keyboard accessible, ensure WCAG AA compliance, and optimize for mobile."


🔄 Maintenance & Scalability

Code Maintenance

  • Keep dependencies updated - Security patches, bug fixes
  • Remove dead code - Delete unused files and functions
  • Refactor regularly - Don't let technical debt accumulate
  • Monitor bundle size - Alert when it grows unexpectedly
  • Track metrics over time - Is performance degrading?

Scaling Checklist

  • Horizontal scaling - Can you add more servers?
  • Database scaling - Read replicas, sharding strategy
  • Caching layers - Redis, CDN, application cache
  • Rate limiting - Protect against abuse and DDoS
  • Queue background jobs - Don't block API responses

Technical Debt Management

  • Track TODOs - File issues for all "TODO" comments
  • Regular refactoring sprints - Dedicate time to clean up
  • Measure code quality - Use tools like SonarQube
  • Set quality gates - Don't allow quality to regress

Example prompt for AI:

"Review this codebase for technical debt. Identify: dead code, outdated dependencies, missing tests, performance bottlenecks, and security issues."


🎓 Prompting AI Effectively

Good Prompt Structure

Context: [What you're building, tech stack, constraints]
Task: [Specific thing you want done]
Requirements: [Constraints, standards, must-haves]
Format: [How you want the output]

Example:
"I'm building a Next.js app with TypeScript and Supabase.

Create an API route that:
- Fetches user profile data from Supabase
- Implements proper error handling
- Includes rate limiting (10 req/min per user)
- Has comprehensive tests
- Follows TypeScript strict mode

Return: The route.ts file, test file, and explanation of security considerations."

Iterative Refinement

  1. Start broad - "Create a user authentication system"
  2. Add constraints - "...using Supabase Auth with email/password"
  3. Request improvements - "Add rate limiting and better error messages"
  4. Ask for review - "Review this for security vulnerabilities"

Ask for Reasoning

  • "Explain why you chose this approach over alternatives"
  • "What are the trade-offs of this solution?"
  • "How would this scale to 1M users?"
  • "What could go wrong with this implementation?"

📋 Quick Reference: AI Prompt Templates

For New Features

Create [feature] for [app type] using [tech stack].

Requirements:
- [Business requirement 1]
- [Business requirement 2]
- Error handling with user-friendly messages
- TypeScript with strict types
- Comprehensive tests (unit + integration)
- Performance optimized for [scale]
- Secure against [specific threats]

Include: Implementation, tests, and documentation.

For Code Review

Review this code for:
1. Security vulnerabilities
2. Performance issues
3. Code quality and maintainability
4. Missing error handling
5. Test coverage gaps
6. Accessibility issues

Provide specific suggestions with code examples.

For Debugging

This code is failing with: [error message]

Context:
- What I expect: [expected behavior]
- What's happening: [actual behavior]
- What I've tried: [debugging steps]

Tech stack: [frameworks/versions]

Help me:
1. Identify root cause
2. Fix the issue
3. Add tests to prevent regression
4. Improve error handling

For Optimization

Optimize this code for [performance/readability/maintainability].

Current issues:
- [Specific problem 1]
- [Specific problem 2]

Constraints:
- Must maintain [requirement]
- Cannot change [constraint]

Provide: Refactored code + explanation of improvements + performance benchmarks.

For Testing

Write comprehensive tests for this [function/component/API].

Cover:
- Happy path
- Edge cases (null, empty, large inputs)
- Error scenarios
- Security concerns

Use [testing framework] and aim for 90%+ coverage.

🎯 Production-Ready Checklist

Before deploying to production, verify:

Functionality

  • All features work as specified
  • Tested on multiple browsers/devices
  • All edge cases handled
  • Error states tested

Performance

  • Lighthouse score > 90
  • API response times < 200ms (p95)
  • No memory leaks
  • Database queries optimized

Security

Reliability

  • Error monitoring configured (Sentry)
  • Logging working correctly
  • Alerts set up for critical failures
  • Database backups automated
  • Rollback plan documented

Quality

  • Test coverage > 70%
  • No console.log in production code
  • TypeScript strict mode enabled
  • Linter passing with no warnings
  • Code reviewed by peer

Documentation

  • README.md complete
  • API documentation current
  • Environment variables documented
  • Deployment instructions clear
  • Monitoring dashboard accessible

💡 Remember

Shipping a working product matters more than making it perfect.

Use this checklist as a guide; you do not have to meet every item before you ship. Early-stage products can ship with: - Basic error handling (not comprehensive monitoring) - Core feature tests (not 100% coverage) - Good-enough performance (not perfectly optimized)

But NEVER compromise on: - Security - Data integrity - User privacy - Core functionality

The Path: 1. Ship MVP → Learn from users 2. Add monitoring → Learn what breaks 3. Improve quality → Iterate based on data 4. Scale thoughtfully → When you have product-market fit

Start with good habits: - Write tests for critical paths - Handle errors gracefully - Document as you go - Review your own code before asking AI to

Good engineers still make mistakes. They are disciplined and careful, and they keep learning.


🔗 Additional Resources


🎁 What Supabase Handles For You

Supabase is a Backend-as-a-Service (BaaS) that eliminates ~40% of typical backend work.

✅ You Don't Need To Build:

Database Infrastructure

  • ✅ PostgreSQL setup & management - No server provisioning, updates, or patches
  • ✅ Connection pooling - Handles thousands of concurrent connections efficiently
  • ✅ Automatic backups - Daily backups on paid plans (you just need to verify restore works)
  • ✅ Replication & high availability - Built-in redundancy (on production plans)
  • ✅ Database migrations - Version control for schema changes (you write SQL, Supabase tracks it)

Authentication System

  • ✅ User registration & login - Email/password, magic links, OAuth (Google, GitHub, etc.)
  • ✅ Password hashing - bcrypt automatically applied
  • ✅ Session management - JWT tokens, refresh tokens, automatic expiry
  • ✅ Email verification - Built-in email confirmation flow
  • ✅ Password reset - Secure reset token generation and validation
  • ✅ Social auth - Pre-built integrations with 20+ providers
  • ✅ Multi-factor authentication - TOTP support built in

Security Features

  • ✅ Row Level Security (RLS) - Database-level access control (you write policies, Supabase enforces)
  • ✅ SQL injection protection - Parameterized queries by default
  • ✅ HTTPS/TLS - All connections encrypted
  • ✅ API key management - Separate anon and service role keys
  • ✅ CORS configuration - Cross-origin request handling

Storage & File Management

  • ✅ Object storage - S3-compatible file storage (images, videos, audio)
  • ✅ Image transformations - Resize, crop, optimize on-the-fly
  • ✅ CDN delivery - Fast global file delivery
  • ✅ Access control - Public/private buckets with RLS policies

Real-time Features

  • ✅ Database subscriptions - Listen to INSERT, UPDATE, DELETE events
  • ✅ WebSocket management - Real-time connections handled automatically
  • ✅ Presence - Track online users (channels feature)
  • ✅ Broadcasting - Send messages between clients

APIs

  • ✅ Auto-generated REST API - Every table gets CRUD endpoints automatically
  • ✅ Auto-generated GraphQL - Optional GraphQL interface
  • ✅ OpenAPI documentation - Swagger docs auto-generated from schema

Developer Experience

  • ✅ Database GUI - Visual table editor, query builder
  • ✅ SQL editor - Run queries directly in dashboard
  • ✅ Logs viewer - See all database queries and errors
  • ✅ Schema visualization - Visual ERD diagrams
  • ✅ Local development - Supabase CLI for offline work

⚠️ You Still Need To Handle:

Application Logic

  • ❌ Business rules - Supabase stores data; you define what's valid
  • ❌ Complex workflows - Multi-step processes need custom API routes
  • ❌ Third-party integrations - OpenAI, Stripe, etc. are your responsibility
  • ❌ Data transformations - Supabase returns raw data; you format it for UI

Quality Assurance

  • ❌ Testing - Write your own tests for RLS policies, API endpoints, edge cases
  • ❌ Monitoring - Add Sentry or similar for application errors
  • ❌ Performance optimization - Create indexes, optimize queries, add caching

Advanced Features

  • ❌ Rate limiting - Implement in your API routes (not built into Supabase)
  • ❌ Complex caching - Redis or similar for computed results
  • ❌ Background jobs - Queue systems for long-running tasks
  • ❌ Advanced search - Full-text search needs manual setup or external service

💰 What This Saves You

Without Supabase, you'd need to build/manage:

Traditional Stack:
├── PostgreSQL server setup (1-2 days)
├── Auth system (1-2 weeks)
│   ├── User registration/login
│   ├── Password reset flow
│   ├── Email verification
│   ├── Session management
│   └── OAuth integrations
├── File upload system (3-5 days)
│   ├── Storage service (S3/GCS)
│   ├── Upload handlers
│   ├── Access control
│   └── CDN setup
├── Real-time infrastructure (1 week)
│   ├── WebSocket server
│   ├── Pub/sub system
│   └── Connection management
├── API layer (1 week)
│   ├── CRUD endpoints
│   ├── Input validation
│   └── API documentation
└── DevOps (ongoing)
    ├── Database backups
    ├── Security patches
    ├── Scaling infrastructure
    └── Monitoring

Total: 4-6 weeks of engineering work

With Supabase:

Your Responsibilities:
├── Define database schema (1-2 days)
├── Write RLS policies (1-2 days)
├── Configure auth providers (1-2 hours)
├── Set up storage buckets (1 hour)
└── Build your application logic

Total: 3-5 days of engineering work

Time saved: ~85% on infrastructure/backend basics

🎯 Best Practices with Supabase

Do This:

  • ✅ Use RLS policies - Never bypass with service role key in client code
  • ✅ Create indexes - Supabase doesn't auto-index (except primary keys)
  • ✅ Write migrations - Track all schema changes in SQL files
  • ✅ Test RLS policies - Verify users can't access unauthorized data
  • ✅ Use TypeScript types - Generate types from your schema
  • ✅ Monitor query performance - Check slow query logs in dashboard
  • ✅ Separate environments - Use different projects for dev/staging/prod

Don't Do This:

  • ❌ Use service role key in frontend - Bypasses all RLS security
  • ❌ Store secrets in database - Use Supabase Vault or environment variables
  • ❌ Skip migrations - Manual schema changes break deployments
  • ❌ Ignore RLS - "I'll add security later" leaves your data exposed until you do
  • ❌ Over-fetch data - Select only columns you need
  • ❌ Forget indexes - Unindexed queries slow down as data grows

📋 Supabase-Specific Checklist

Before going to production: - [ ] RLS enabled on all tables - No public access without policies - [ ] Policies tested - Verify users can't access others' data - [ ] Indexes created - On foreign keys, commonly queried columns - [ ] Backups verified - Test restoring from backup - [ ] Storage buckets configured - Public vs private correctly set - [ ] Auth providers working - Test all login methods - [ ] Environment variables set - Anon key (client), Service key (server only) - [ ] Connection limits understood - Know your plan's limits - [ ] Migrations in version control - All schema changes tracked

🔧 Example: What You Write vs What Supabase Handles

Traditional Backend (You Write Everything):

// 200+ lines of auth code
app.post('/api/register', async (req, res) => {
  // 1. Validate email format
  // 2. Check if email exists
  // 3. Hash password with bcrypt
  // 4. Generate verification token
  // 5. Send verification email
  // 6. Store user in database
  // 7. Create session
  // 8. Return JWT token
})

With Supabase (You Write This):

// 3 lines
const { data, error } = await supabase.auth.signUp({
  email, password
})
// Supabase handles: hashing, verification email, session, everything

Traditional Data Access (Manual Security):

// You must remember to check permissions
app.get('/api/user-data', async (req, res) => {
  // 1. Verify JWT token
  // 2. Extract user ID from token
  // 3. Query database with user ID filter
  // 4. Check if user owns the data
  // 5. Return data or 403 error
})

With Supabase RLS (Automatic Security):

-- Write once, enforced forever
CREATE POLICY "Users can only see their own data"
  ON user_data FOR SELECT
  USING (auth.uid() = user_id);
// Client code - RLS automatically enforces security
const { data } = await supabase
  .from('user_data')
  .select('*')
// User CANNOT access other users' data, even if they try


Last Updated: October 2025