This file provides guidance to LLM/AI Agents when working with code in this repository.
Residents is a production-ready TypeScript backend API framework designed as a foundational user management system. It's a "batteries-included" authentication and user management starter that can be used as a standalone API or published as an NPM package.
Tech Stack:
- Express 5 with TypeScript (strict mode)
- PostgreSQL with Drizzle ORM
- JWT + Passport.js authentication
- SendGrid for email
- Docker for containerization
npm run dev # Start development server with hot reload
npm test # Run Jest tests
npm run lint # ESLint checking
npm run prettier # Format code
npm run typecoverage # Check type coverage (must be β₯90%)npm run dockerup # Start PostgreSQL container
npm run push # Push schema to database (Drizzle's push workflow)
npm run studio # Open Drizzle Studio GUI
npm run seed # Seed test users
npm run seed:owner # Create initial admin usernpm run build # Compile TypeScript to dist/
npm start # Run production server (requires build)
docker-compose up # Run full stack with databasenpm test # Run all tests
npm run test:watch # Run tests in watch mode
npm run test:coverage # Generate coverage reportAll business logic is centralized in /src/services/index.ts which exports a single SERVICES object. Controllers should be thin and delegate to services:
// β
Good - Controller delegates to service
const user = await SERVICES.getUserById(userId)
// β Bad - Business logic in controller
const user = await db.query.users.findFirst({...})Use custom error classes from /src/errors/:
ValidationError- 400 Bad RequestUnauthorizedError- 401 UnauthorizedForbiddenError- 403 ForbiddenNotFoundError- 404 Not FoundConflictError- 409 Conflict
Errors automatically propagate to the centralized error handler middleware.
Core tables in /src/db/schema/:
users- User accounts with role/status enumstokens- Multi-purpose tokens (refresh, magic, reset, validation)user_meta- Extensible user metadatafederated_credentials- Social auth providers
Uses CUID2 for IDs, soft delete via deleted_at, and PostgreSQL enums for type safety.
- JWT access tokens (15 min) stored in cookies
- Refresh tokens (7 days) stored in database
- CSRF protection via
sameSite: stricton all auth cookies - Support for local auth, Google OAuth, and magic links
Tests are co-located with source files (.test.ts):
src/services/index.ts # Service implementation
src/services/index.test.ts # Unit testsMock the SERVICES object for testing:
jest.mock('../services', () => ({
SERVICES: { getUserById: jest.fn() }
}))Hierarchical roles in /src/utils/rbac.ts:
superadmin>admin>moderator>member>user
Use RBAC middleware to protect routes:
router.delete('/:id',
authenticate,
rbacDeleteUser, // Checks role hierarchy
deleteUser
)Different token purposes with specific expiration:
access- JWT authentication (15 min)refresh- Token refresh (7 days)magic- Magic link login (10 min)reset- Password reset (1 hour)validation- Email verification (24 hours)
Required .env variables:
DATABASE_URL # PostgreSQL connection string
JWT_SECRET # JWT signing secret
SENDGRID_API_KEY # Email service (optional)
GOOGLE_CLIENT_ID # Google OAuth (optional)
GOOGLE_CLIENT_SECRET # Google OAuth (optional)See .env.example for all configuration options including token expiration times.
Consistent response structure:
// Success
{ success: true, data: {...} }
// Error
{ success: false, error: "Error message" }- Before starting: Run
npm run dockerupto start PostgreSQL - Initialize database: Run
npm run pushto create schema - Seed data: Run
npm run seedfor test users - Start development: Run
npm run dev - Before committing: Ensure
npm run lintandnpm testpass
- TypeScript: Strict mode, explicit return types required (
@typescript-eslint/explicit-function-return-type) - Imports: Absolute paths from src/, group by: external libs, internal modules, types
- Formatting: Single quotes, no semicolons, 2-space indent, 120 char line width
- Naming: camelCase for variables/functions, PascalCase for classes/types, UPPER_SNAKE_CASE for constants
- Error handling: Custom error classes with statusCode property (BadRequestError, ValidationError, etc.)
- Types: No
anyallowed, use proper interfaces, leverage strict TypeScript - Functions: Async/await preferred, explicit return types, validate inputs early
- Files: Controllers in
/controllers, services in/services, middleware patterns - Tests: Jest with
.test.tssuffix, 90%+ coverage required, mock external dependencies, test edge cases - Services: All business logic in services, not controllers
- Database: Use Drizzle ORM methods, not raw SQL
- Security: Never log sensitive data, use parameterized queries
- Define route in
/src/routes/ - Create controller in
/src/controllers/ - Implement business logic in
/src/services/index.ts - Add validation middleware if needed
- Write unit tests for service methods
- Update Postman collection
- Install passport strategy package
- Configure in
/src/passport/ - Add provider credentials to
.env - Create auth routes in
/src/routes/auth.ts - Update
federated_credentialstable if needed
- Update schema in
/src/db/schema/ - Run
npm run pushto apply the schema to your database - Update related services and types
- Express 5: Using cutting-edge Express 5, some middleware may need updates
- Type Coverage: Must maintain β₯90% type coverage (enforced by pre-commit)
- NEVER push to main: Always create feature branches and PRs
- Tests required: All new features must include tests
- Security first: Follow OWASP best practices for authentication