// Config Record
>.replit.md — Full-Stack Web App
Replit agent rules for React + Express + PostgreSQL projects with guardrails tuned for cloud IDE workflows.
author:
dotmd Team
license:CC0
published:Feb 23, 2026
// Installation
>Add this file to your project repository:
- Replit--path=
.replit.md
// File Content
.replit.md
1# Full-Stack Web App — Replit Agent Instructions23React + Express + PostgreSQL monorepo. Vite dev server proxied through Express. PostgreSQL via Replit's managed database.45## Quick Reference67| Item | Value |8|---|---|9| Node version | 20 LTS |10| Package manager | npm (not yarn, not pnpm) |11| Frontend | React 18 + Vite 5 |12| Backend | Express 4.x + TypeScript |13| Database | PostgreSQL (Replit Postgres) |14| ORM | Drizzle ORM + drizzle-kit |15| Auth | express-session + connect-pg-simple |16| Port (server) | 5000 |17| Port (Vite dev) | 5173 (proxied, never exposed directly) |18| Secrets | Replit Secrets panel only — never .env files |1920## Project Structure2122```23├── client/ # React frontend24│ ├── src/25│ │ ├── components/ # Reusable UI components26│ │ ├── pages/ # Route-level components27│ │ ├── hooks/ # Custom React hooks28│ │ ├── lib/ # API client, utils29│ │ └── main.tsx30│ ├── index.html31│ └── vite.config.ts32├── server/ # Express backend33│ ├── routes/ # Route modules (one file per resource)34│ ├── middleware/ # Auth, validation, error handling35│ ├── db/36│ │ ├── schema.ts # Drizzle schema (single source of truth)37│ │ ├── index.ts # DB connection + drizzle instance38│ │ └── seed.ts # Seed data (idempotent)39│ ├── index.ts # Server entry point40│ └── vite.ts # Vite dev middleware setup41├── shared/ # Shared types between client/server42│ └── types.ts43├── drizzle.config.ts44├── tsconfig.json45├── package.json # Single root package.json (no workspaces)46└── .replit47```4849**Do not** create separate `client/package.json` or `server/package.json`. This is a single-package monorepo — all dependencies in the root `package.json`.5051## Tech Stack — Pinned Versions5253Install these exact packages. Do not substitute alternatives.5455```json56{57 "dependencies": {58 "react": "^18.3.1",59 "react-dom": "^18.3.1",60 "wouter": "^3.3.5",61 "@tanstack/react-query": "^5.60.5",62 "express": "^4.21.1",63 "express-session": "^1.18.1",64 "connect-pg-simple": "^10.0.0",65 "drizzle-orm": "^0.38.3",66 "@neondatabase/serverless": "^0.10.4",67 "ws": "^8.18.0",68 "zod": "^3.23.8",69 "tailwindcss": "^4.0.0",70 "@tailwindcss/vite": "^4.0.0"71 },72 "devDependencies": {73 "vite": "^5.4.14",74 "@vitejs/plugin-react": "^4.3.4",75 "drizzle-kit": "^0.30.1",76 "tsx": "^4.19.2",77 "typescript": "^5.6.3",78 "@types/express": "^4.17.21",79 "@types/express-session": "^1.18.1",80 "@types/node": "^22.10.2",81 "esbuild": "^0.24.0"82 }83}84```8586**Banned packages** — do not install these under any circumstances:8788| Package | Reason |89|---|---|90| `next`, `remix`, `astro` | This is not a framework project |91| `sequelize`, `typeorm`, `prisma` | Use Drizzle only |92| `passport` | Overkill — use express-session directly |93| `dotenv` | Use Replit Secrets, not .env |94| `nodemon` | Use tsx --watch |95| `cors` | Not needed — single origin, Vite proxied |96| `body-parser` | Built into Express 4.16+ |97| `create-react-app` | Dead. Use Vite. |9899## Secrets & Environment Variables100101**Never create `.env` files.** Use the Replit Secrets panel (lock icon in sidebar).102103Required secrets:104105| Secret | Value | Notes |106|---|---|---|107| `DATABASE_URL` | Auto-set by Replit Postgres | Do not manually set |108| `SESSION_SECRET` | Random 64-char hex string | Generate with `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` |109110Access in code:111112```typescript113// ✅ Correct114const dbUrl = process.env.DATABASE_URL;115116// ❌ Wrong — never do this117import dotenv from 'dotenv';118dotenv.config();119```120121## Database122123### Connection Setup124125Use Neon's serverless driver with WebSocket support (required for Replit's Postgres proxy):126127```typescript128// server/db/index.ts129import { Pool, neonConfig } from '@neondatabase/serverless';130import { drizzle } from 'drizzle-orm/neon-serverless';131import ws from 'ws';132import * as schema from './schema';133134neonConfig.webSocketConstructor = ws;135136const pool = new Pool({ connectionString: process.env.DATABASE_URL });137export const db = drizzle(pool, { schema });138```139140**Do not** use `pg` or `postgres` packages directly. The Neon serverless driver is required for Replit's database proxy.141142### Schema Rules143144- Define all tables in `server/db/schema.ts` — single file, no splitting145- Use `serial` for primary keys, `timestamp` for dates146- Every table gets `createdAt` and `updatedAt` columns147- Export Drizzle `insertSchema` with Zod for validation148149```typescript150// server/db/schema.ts151import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';152import { createInsertSchema } from 'drizzle-zod';153154export const users = pgTable('users', {155 id: serial('id').primaryKey(),156 username: text('username').notNull().unique(),157 password: text('password').notNull(),158 createdAt: timestamp('created_at').defaultNow().notNull(),159});160161export const insertUserSchema = createInsertSchema(users).omit({162 id: true,163 createdAt: true,164});165```166167### Migrations168169```bash170# Generate migration after schema changes171npx drizzle-kit generate172173# Push schema directly (dev only — fine for Replit)174npx drizzle-kit push175```176177Use `drizzle-kit push` during development. Do not generate migration files unless preparing for production deployment.178179## Server Architecture180181### Entry Point182183The server entry (`server/index.ts`) must:1841851. Create Express app with `express.json()` and `express.urlencoded({ extended: false })`1862. Configure `express-session` with `connect-pg-simple` store (using `DATABASE_URL`)1873. Register API routes, then mount Vite dev middleware1884. **Listen on port 5000, bound to `0.0.0.0`** — Replit requires both. Do not use 3000/8080 or bind to localhost.189190### Route Organization191192- One file per resource in `server/routes/`, each exporting an Express `Router`193- **All API routes must be prefixed with `/api/`** — prevents conflicts with client-side routing194- Register all routers in `server/routes/index.ts` via a `registerRoutes(app)` function195- Wrap async route handlers in try/catch, call `next(error)` on failure196- Add a global error handler middleware at the end of the middleware chain197198## Frontend Architecture199200### Vite Config201202- Root: `./client`, build output: `../dist/public`203- Plugins: `@vitejs/plugin-react` only204- Vite dev server is **not exposed** — Express proxies it via `server/vite.ts`205206### API Client207208Use `@tanstack/react-query` for all server state. Create a single fetch wrapper in `client/src/lib/api.ts` that handles JSON serialization, `credentials: 'include'`, and error extraction. **Do not install axios** — Fetch API is sufficient.209210### Routing211212Use `wouter` with `<Switch>` and `<Route>`. Do not install `react-router-dom`.213214### Styling215216Tailwind CSS via the Vite plugin. No CSS-in-JS, no styled-components, no CSS modules.217218## Agent Boundaries — READ THIS219220### Do Not221222- **Do not run `npm install` without listing exact packages.** No `npm install` with no arguments — always specify what you're installing.223- **Do not modify the Nix configuration** (`.replit` nix channel or `replit.nix`) unless explicitly asked.224- **Do not create new config files** (`.prettierrc`, `.eslintrc`, `jest.config`, `.babelrc`). The project uses TypeScript strict mode and Vite — no additional tooling.225- **Do not restructure the project layout** described above. Add files within the existing structure.226- **Do not add `"type": "module"` to package.json.** Use tsx for server execution which handles ESM/CJS interop.227- **Do not install UI component libraries** (MUI, Chakra, Ant Design, shadcn) unless explicitly requested.228229### When Adding a Feature2302311. Define the schema in `server/db/schema.ts`2322. Run `npx drizzle-kit push`2333. Create the route file in `server/routes/`2344. Register the route in `server/routes/index.ts`2355. Add the React Query hook in `client/src/hooks/`2366. Build the page/component in `client/src/`237238Always follow this order. Do not build frontend before the API exists.239240## Scripts241242```json243{244 "scripts": {245 "dev": "tsx server/index.ts",246 "build": "vite build && esbuild server/index.ts --bundle --platform=node --outdir=dist --packages=external",247 "start": "NODE_ENV=production node dist/index.js",248 "db:push": "drizzle-kit push",249 "db:generate": "drizzle-kit generate",250 "db:seed": "tsx server/db/seed.ts"251 }252}253```254255The `dev` script starts the Express server, which internally mounts Vite as middleware. There is no separate `vite dev` command.256257## .replit Configuration258259```toml260run = "npm run dev"261entrypoint = "server/index.ts"262263[deployment]264run = ["sh", "-c", "npm run build && npm run start"]265deploymentTarget = "cloudrun"266267[[ports]]268localPort = 5000269externalPort = 80270```271272## Testing273274Use Vitest for unit tests only. Do not set up e2e testing.275276```bash277npm install -D vitest278```279280Test files go next to the code they test: `users.test.ts` beside `users.ts`.281282Focus tests on:283- Zod schema validation (shared types)284- Utility functions (pure logic)285- API response shapes (lightweight integration)286287Do not mock the database. For route tests, use the real db with a test seed.288289## Common Patterns290291### Shared Validation292293```typescript294// shared/types.ts295import { z } from 'zod';296297export const createPostSchema = z.object({298 title: z.string().min(1).max(200),299 content: z.string().min(1),300});301302export type CreatePost = z.infer<typeof createPostSchema>;303```304305Use in both server (request validation) and client (form validation).306307### Auth Check Middleware308309```typescript310// server/middleware/auth.ts311import type { RequestHandler } from 'express';312313export const requireAuth: RequestHandler = (req, res, next) => {314 if (!req.session.userId) {315 return res.status(401).json({ message: 'Unauthorized' });316 }317 next();318};319```320321### Password Hashing322323Use the Node.js built-in `crypto.scrypt` — do not install bcrypt (native compilation issues on Replit):324325```typescript326import { scrypt, randomBytes, timingSafeEqual } from 'crypto';327import { promisify } from 'util';328329const scryptAsync = promisify(scrypt);330331export async function hashPassword(password: string): Promise<string> {332 const salt = randomBytes(16).toString('hex');333 const buf = (await scryptAsync(password, salt, 64)) as Buffer;334 return `${buf.toString('hex')}.${salt}`;335}336337export async function comparePasswords(supplied: string, stored: string): Promise<boolean> {338 const [hashed, salt] = stored.split('.');339 const buf = (await scryptAsync(supplied, salt, 64)) as Buffer;340 return timingSafeEqual(Buffer.from(hashed, 'hex'), buf);341}342```343344### React Query Pattern345346- Query keys use the API path: `queryKey: ['/api/posts']`347- Mutations call `apiRequest()` and invalidate related query keys on success348- One hook file per resource in `client/src/hooks/` (e.g. `use-posts.ts`)349- Import shared types from `../../../shared/types` for mutation input typing350