dotmd
// 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
    .replit.md
// File Content
.replit.md
1# Full-Stack Web App — Replit Agent Instructions
2
3React + Express + PostgreSQL monorepo. Vite dev server proxied through Express. PostgreSQL via Replit's managed database.
4
5## Quick Reference
6
7| 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 |
19
20## Project Structure
21
22```
23├── client/ # React frontend
24│ ├── src/
25│ │ ├── components/ # Reusable UI components
26│ │ ├── pages/ # Route-level components
27│ │ ├── hooks/ # Custom React hooks
28│ │ ├── lib/ # API client, utils
29│ │ └── main.tsx
30│ ├── index.html
31│ └── vite.config.ts
32├── server/ # Express backend
33│ ├── routes/ # Route modules (one file per resource)
34│ ├── middleware/ # Auth, validation, error handling
35│ ├── db/
36│ │ ├── schema.ts # Drizzle schema (single source of truth)
37│ │ ├── index.ts # DB connection + drizzle instance
38│ │ └── seed.ts # Seed data (idempotent)
39│ ├── index.ts # Server entry point
40│ └── vite.ts # Vite dev middleware setup
41├── shared/ # Shared types between client/server
42│ └── types.ts
43├── drizzle.config.ts
44├── tsconfig.json
45├── package.json # Single root package.json (no workspaces)
46└── .replit
47```
48
49**Do not** create separate `client/package.json` or `server/package.json`. This is a single-package monorepo — all dependencies in the root `package.json`.
50
51## Tech Stack — Pinned Versions
52
53Install these exact packages. Do not substitute alternatives.
54
55```json
56{
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```
85
86**Banned packages** — do not install these under any circumstances:
87
88| 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. |
98
99## Secrets & Environment Variables
100
101**Never create `.env` files.** Use the Replit Secrets panel (lock icon in sidebar).
102
103Required secrets:
104
105| 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'))"` |
109
110Access in code:
111
112```typescript
113// ✅ Correct
114const dbUrl = process.env.DATABASE_URL;
115
116// ❌ Wrong — never do this
117import dotenv from 'dotenv';
118dotenv.config();
119```
120
121## Database
122
123### Connection Setup
124
125Use Neon's serverless driver with WebSocket support (required for Replit's Postgres proxy):
126
127```typescript
128// server/db/index.ts
129import { Pool, neonConfig } from '@neondatabase/serverless';
130import { drizzle } from 'drizzle-orm/neon-serverless';
131import ws from 'ws';
132import * as schema from './schema';
133
134neonConfig.webSocketConstructor = ws;
135
136const pool = new Pool({ connectionString: process.env.DATABASE_URL });
137export const db = drizzle(pool, { schema });
138```
139
140**Do not** use `pg` or `postgres` packages directly. The Neon serverless driver is required for Replit's database proxy.
141
142### Schema Rules
143
144- Define all tables in `server/db/schema.ts` — single file, no splitting
145- Use `serial` for primary keys, `timestamp` for dates
146- Every table gets `createdAt` and `updatedAt` columns
147- Export Drizzle `insertSchema` with Zod for validation
148
149```typescript
150// server/db/schema.ts
151import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
152import { createInsertSchema } from 'drizzle-zod';
153
154export 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});
160
161export const insertUserSchema = createInsertSchema(users).omit({
162 id: true,
163 createdAt: true,
164});
165```
166
167### Migrations
168
169```bash
170# Generate migration after schema changes
171npx drizzle-kit generate
172
173# Push schema directly (dev only — fine for Replit)
174npx drizzle-kit push
175```
176
177Use `drizzle-kit push` during development. Do not generate migration files unless preparing for production deployment.
178
179## Server Architecture
180
181### Entry Point
182
183The server entry (`server/index.ts`) must:
184
1851. 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 middleware
1884. **Listen on port 5000, bound to `0.0.0.0`** — Replit requires both. Do not use 3000/8080 or bind to localhost.
189
190### Route Organization
191
192- 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 routing
194- Register all routers in `server/routes/index.ts` via a `registerRoutes(app)` function
195- Wrap async route handlers in try/catch, call `next(error)` on failure
196- Add a global error handler middleware at the end of the middleware chain
197
198## Frontend Architecture
199
200### Vite Config
201
202- Root: `./client`, build output: `../dist/public`
203- Plugins: `@vitejs/plugin-react` only
204- Vite dev server is **not exposed** — Express proxies it via `server/vite.ts`
205
206### API Client
207
208Use `@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.
209
210### Routing
211
212Use `wouter` with `<Switch>` and `<Route>`. Do not install `react-router-dom`.
213
214### Styling
215
216Tailwind CSS via the Vite plugin. No CSS-in-JS, no styled-components, no CSS modules.
217
218## Agent Boundaries — READ THIS
219
220### Do Not
221
222- **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.
228
229### When Adding a Feature
230
2311. 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/`
237
238Always follow this order. Do not build frontend before the API exists.
239
240## Scripts
241
242```json
243{
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```
254
255The `dev` script starts the Express server, which internally mounts Vite as middleware. There is no separate `vite dev` command.
256
257## .replit Configuration
258
259```toml
260run = "npm run dev"
261entrypoint = "server/index.ts"
262
263[deployment]
264run = ["sh", "-c", "npm run build && npm run start"]
265deploymentTarget = "cloudrun"
266
267[[ports]]
268localPort = 5000
269externalPort = 80
270```
271
272## Testing
273
274Use Vitest for unit tests only. Do not set up e2e testing.
275
276```bash
277npm install -D vitest
278```
279
280Test files go next to the code they test: `users.test.ts` beside `users.ts`.
281
282Focus tests on:
283- Zod schema validation (shared types)
284- Utility functions (pure logic)
285- API response shapes (lightweight integration)
286
287Do not mock the database. For route tests, use the real db with a test seed.
288
289## Common Patterns
290
291### Shared Validation
292
293```typescript
294// shared/types.ts
295import { z } from 'zod';
296
297export const createPostSchema = z.object({
298 title: z.string().min(1).max(200),
299 content: z.string().min(1),
300});
301
302export type CreatePost = z.infer<typeof createPostSchema>;
303```
304
305Use in both server (request validation) and client (form validation).
306
307### Auth Check Middleware
308
309```typescript
310// server/middleware/auth.ts
311import type { RequestHandler } from 'express';
312
313export const requireAuth: RequestHandler = (req, res, next) => {
314 if (!req.session.userId) {
315 return res.status(401).json({ message: 'Unauthorized' });
316 }
317 next();
318};
319```
320
321### Password Hashing
322
323Use the Node.js built-in `crypto.scrypt` — do not install bcrypt (native compilation issues on Replit):
324
325```typescript
326import { scrypt, randomBytes, timingSafeEqual } from 'crypto';
327import { promisify } from 'util';
328
329const scryptAsync = promisify(scrypt);
330
331export 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}
336
337export 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```
343
344### React Query Pattern
345
346- Query keys use the API path: `queryKey: ['/api/posts']`
347- Mutations call `apiRequest()` and invalidate related query keys on success
348- One hook file per resource in `client/src/hooks/` (e.g. `use-posts.ts`)
349- Import shared types from `../../../shared/types` for mutation input typing
350