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
dangerouslySetInnerHTMLorrehype-rawon that text, user-supplied links accept onlyhttpandhttps, 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.logprints (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 auditgives 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.jsonin 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)¶
- Unit Tests (70%) - Test individual functions in isolation
- Integration Tests (20%) - Test how components work together
- 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()notget() - 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 orunknown - 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¶
- Start broad - "Create a user authentication system"
- Add constraints - "...using Supabase Auth with email/password"
- Request improvements - "Add rate limiting and better error messages"
- 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¶
- Every check in Security passes
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¶
- OWASP Top 10 - Security vulnerabilities
- Web.dev - Performance and best practices
- Refactoring.Guru - Code patterns and anti-patterns
- 12 Factor App - Building SaaS applications
- System Design Primer - Scalability
🎁 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