create-xpress-backend 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +725 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
# create-xpress-backend
|
|
2
|
+
|
|
3
|
+
> Scaffold a production-ready **Express + Prisma + PostgreSQL** backend in seconds.
|
|
4
|
+
> Choose **TypeScript** (default) or **JavaScript** — everything wired up, zero boilerplate.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx create-xpress-backend
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Requirements](#requirements)
|
|
15
|
+
- [How to Use the CLI](#how-to-use-the-cli)
|
|
16
|
+
- [TypeScript vs JavaScript](#typescript-vs-javascript)
|
|
17
|
+
- [What Gets Scaffolded](#what-gets-scaffolded)
|
|
18
|
+
- [Project Structure Explained](#project-structure-explained)
|
|
19
|
+
- [Every File — What It Does](#every-file--what-it-does)
|
|
20
|
+
- [Environment Variables](#environment-variables)
|
|
21
|
+
- [First-Time Setup (Step by Step)](#first-time-setup-step-by-step)
|
|
22
|
+
- [Available Scripts](#available-scripts)
|
|
23
|
+
- [API Reference](#api-reference)
|
|
24
|
+
- [How the Architecture Works](#how-the-architecture-works)
|
|
25
|
+
- [Adding a New Module](#adding-a-new-module)
|
|
26
|
+
- [File Upload System](#file-upload-system)
|
|
27
|
+
- [JWT Auth System](#jwt-auth-system)
|
|
28
|
+
- [Database Workflows](#database-workflows)
|
|
29
|
+
- [Deployment Checklist](#deployment-checklist)
|
|
30
|
+
- [FAQ](#faq)
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
| Requirement | Minimum | Check |
|
|
37
|
+
|-------------|---------|-------|
|
|
38
|
+
| Node.js | ≥ 18.0.0 | `node -v` |
|
|
39
|
+
| npm | ≥ 8.0.0 | `npm -v` |
|
|
40
|
+
| PostgreSQL | ≥ 13 | running locally or on a cloud service |
|
|
41
|
+
|
|
42
|
+
> PostgreSQL can be local, Docker, or a hosted service — Supabase, Neon, Railway, Render, etc.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## How to Use the CLI
|
|
47
|
+
|
|
48
|
+
No global install needed. Just run:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx create-xpress-backend
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You will see the banner and then a short series of prompts:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
┌─────────────────────────────────────────────┐
|
|
58
|
+
│ 🚀 create-xpress-backend v1.0.0 │
|
|
59
|
+
│ Express · Prisma · PostgreSQL · Node.js │
|
|
60
|
+
└─────────────────────────────────────────────┘
|
|
61
|
+
|
|
62
|
+
project_name : my-api
|
|
63
|
+
language : ❯ TypeScript (recommended)
|
|
64
|
+
JavaScript
|
|
65
|
+
port : 5050
|
|
66
|
+
file upload system : include? (Y/n)
|
|
67
|
+
JWT auth module : include? (y/N)
|
|
68
|
+
git init : initialize? (Y/n)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Prompt Reference
|
|
72
|
+
|
|
73
|
+
| Prompt | What it does | Default |
|
|
74
|
+
|--------|-------------|---------|
|
|
75
|
+
| `project_name` | Folder name + npm package name. Lowercase letters, numbers, hyphens, underscores only. | — |
|
|
76
|
+
| `language` | TypeScript or JavaScript. Arrow-key selection. | **TypeScript** |
|
|
77
|
+
| `port` | Port written into `.env` and used in startup logs. | `5050` |
|
|
78
|
+
| `file upload system` | Adds Multer, an `/uploads` directory, and upload/list/delete routes. | Yes |
|
|
79
|
+
| `JWT auth module` | Adds register + login endpoints and a JWT guard middleware. | No |
|
|
80
|
+
| `git init` | Runs `git init` inside the generated folder. | Yes |
|
|
81
|
+
|
|
82
|
+
After confirming, the CLI scaffolds everything and prints:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
✔ Done! Project "my-api" created! (TypeScript)
|
|
86
|
+
|
|
87
|
+
Get started:
|
|
88
|
+
cd my-api
|
|
89
|
+
npm install
|
|
90
|
+
# configure DATABASE_URL in .env, then:
|
|
91
|
+
npm run db:push
|
|
92
|
+
npm run dev
|
|
93
|
+
|
|
94
|
+
Build for production:
|
|
95
|
+
npm run build
|
|
96
|
+
npm start
|
|
97
|
+
|
|
98
|
+
Health check → http://localhost:5050/health
|
|
99
|
+
Prisma Studio → npm run db:studio
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## TypeScript vs JavaScript
|
|
105
|
+
|
|
106
|
+
The CLI generates a **complete**, properly typed codebase for whichever language you pick. There is no "TS wrapper around JS" — every file is written natively.
|
|
107
|
+
|
|
108
|
+
| | TypeScript | JavaScript |
|
|
109
|
+
|--|-----------|-----------|
|
|
110
|
+
| Source files | `.ts` | `.js` |
|
|
111
|
+
| Module system | ES module imports (`import/export`) | CommonJS (`require/module.exports`) |
|
|
112
|
+
| Dev server | `ts-node-dev` | `nodemon` |
|
|
113
|
+
| Build step | `tsc` → `dist/` | None — runs directly |
|
|
114
|
+
| Start (prod) | `node dist/server.js` | `node src/server.js` |
|
|
115
|
+
| Type checking | ✅ Strict mode | — |
|
|
116
|
+
| `tsconfig.json` | ✅ Included | — |
|
|
117
|
+
| `src/types/express.d.ts` | ✅ Included (`req.user` typed) | — |
|
|
118
|
+
| Type packages | `@types/*` included | — |
|
|
119
|
+
|
|
120
|
+
> **Recommendation:** Use TypeScript. It catches bugs at compile time and makes refactoring safer — especially important as your API grows.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## What Gets Scaffolded
|
|
125
|
+
|
|
126
|
+
Depending on your choices, between **17 and 25 files** are generated:
|
|
127
|
+
|
|
128
|
+
| Language | Base | + Upload | + Auth | + Both |
|
|
129
|
+
|----------|------|----------|--------|--------|
|
|
130
|
+
| TypeScript | 19 | 20 | 24 | 25 |
|
|
131
|
+
| JavaScript | 17 | 18 | 22 | 23 |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Project Structure Explained
|
|
136
|
+
|
|
137
|
+
### TypeScript
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
my-api/
|
|
141
|
+
├── prisma/
|
|
142
|
+
│ └── schema.prisma ← database models
|
|
143
|
+
├── src/
|
|
144
|
+
│ ├── app.ts ← Express config (middleware, routes, error handler)
|
|
145
|
+
│ ├── server.ts ← HTTP server + graceful shutdown
|
|
146
|
+
│ ├── types/
|
|
147
|
+
│ │ └── express.d.ts ← augments req.user with JwtPayload type
|
|
148
|
+
│ ├── database/
|
|
149
|
+
│ │ ├── prisma.ts ← shared PrismaClient singleton
|
|
150
|
+
│ │ └── seed.ts ← database seeder
|
|
151
|
+
│ ├── middleware/
|
|
152
|
+
│ │ ├── validate.ts ← Joi validation wrapper
|
|
153
|
+
│ │ ├── upload.ts ← Multer config (if uploads selected)
|
|
154
|
+
│ │ └── auth.ts ← JWT guard middleware (if auth selected)
|
|
155
|
+
│ └── modules/
|
|
156
|
+
│ ├── index.ts ← central router
|
|
157
|
+
│ ├── demo/ ← example CRUD module
|
|
158
|
+
│ │ ├── demo.controller.ts
|
|
159
|
+
│ │ ├── demo.service.ts
|
|
160
|
+
│ │ ├── demo.routes.ts
|
|
161
|
+
│ │ └── demo.validation.ts
|
|
162
|
+
│ └── auth/ ← auth module (if auth selected)
|
|
163
|
+
│ ├── auth.controller.ts
|
|
164
|
+
│ ├── auth.service.ts
|
|
165
|
+
│ ├── auth.routes.ts
|
|
166
|
+
│ └── auth.validation.ts
|
|
167
|
+
├── uploads/ ← file storage (if uploads selected)
|
|
168
|
+
├── tsconfig.json
|
|
169
|
+
├── prisma.config.ts
|
|
170
|
+
├── .env
|
|
171
|
+
├── .env.example
|
|
172
|
+
├── .gitignore
|
|
173
|
+
└── package.json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### JavaScript
|
|
177
|
+
|
|
178
|
+
Same structure but with `.js` extensions, `require/module.exports`, no `tsconfig.json`, no `types/` folder.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Every File — What It Does
|
|
183
|
+
|
|
184
|
+
### `src/server.ts` / `src/server.js`
|
|
185
|
+
Entry point. Creates an `http.Server` around the Express app, starts listening on `PORT`, logs startup info, and handles `SIGINT`/`SIGTERM` for graceful shutdown with a 5-second forced-exit fallback.
|
|
186
|
+
|
|
187
|
+
### `src/app.ts` / `src/app.js`
|
|
188
|
+
Configures the Express app — does **not** start the server. Middleware stack applied in order:
|
|
189
|
+
1. `helmet()` — secure HTTP headers
|
|
190
|
+
2. `cors()` — CORS from `CLIENT_URL`
|
|
191
|
+
3. `morgan('dev')` — request logging
|
|
192
|
+
4. `express.json()` — JSON body parsing (10 MB limit)
|
|
193
|
+
5. `express.urlencoded()` — form body parsing
|
|
194
|
+
6. `/uploads` static route — serves uploaded files (if uploads enabled)
|
|
195
|
+
7. `/api` — all module routes
|
|
196
|
+
8. `/health` — health check
|
|
197
|
+
9. 404 handler
|
|
198
|
+
10. Central error handler — anything passed to `next(error)` lands here
|
|
199
|
+
|
|
200
|
+
### `src/database/prisma.ts` / `prisma.js`
|
|
201
|
+
Creates and exports a **single shared PrismaClient** using the `@prisma/adapter-pg` driver adapter with a `pg` connection pool. Every module imports from here.
|
|
202
|
+
|
|
203
|
+
### `src/database/seed.ts` / `seed.js`
|
|
204
|
+
Standalone seeder. Safe to run multiple times — checks for existing records before inserting. Extend this to seed any initial data your app needs.
|
|
205
|
+
|
|
206
|
+
### `prisma/schema.prisma`
|
|
207
|
+
Defines your database models. Base includes `User`. If file uploads were selected, `File` is also included with a `User` relation. Edit here, then run `npm run db:push`.
|
|
208
|
+
|
|
209
|
+
### `src/types/express.d.ts` *(TypeScript only)*
|
|
210
|
+
Augments Express's `Request` interface to add `user?: string | JwtPayload`. This is what makes `req.user` type-safe in every controller and middleware.
|
|
211
|
+
|
|
212
|
+
### `src/middleware/validate.ts` / `validate.js`
|
|
213
|
+
Takes a Joi schema, returns Express middleware. On failure, returns `400` with the first validation error. Usage:
|
|
214
|
+
```ts
|
|
215
|
+
router.post('/', validate(createSchema), controller.create);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `src/middleware/upload.ts` / `upload.js` *(if uploads selected)*
|
|
219
|
+
Multer configured with disk storage, unique filenames, file type filtering (JPEG/PNG/GIF/PDF/TXT/DOC/DOCX), and size limit from `MAX_FILE_SIZE` env var.
|
|
220
|
+
|
|
221
|
+
### `src/middleware/auth.ts` / `auth.js` *(if auth selected)*
|
|
222
|
+
Reads `Authorization: Bearer <token>`, verifies against `JWT_SECRET`, attaches decoded payload to `req.user`. Returns `401` for missing or invalid tokens.
|
|
223
|
+
```ts
|
|
224
|
+
router.get('/me', authMiddleware, controller.getMe);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### `src/modules/index.ts` / `index.js`
|
|
228
|
+
The central router. Add one line per new module:
|
|
229
|
+
```ts
|
|
230
|
+
router.use('/users', usersRoutes);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Demo Module (`src/modules/demo/`)
|
|
234
|
+
Working example of the **Controller → Service → Routes → Validation** pattern:
|
|
235
|
+
- **Validation** — Joi schemas for create and update
|
|
236
|
+
- **Routes** — maps HTTP verbs + paths to controller methods, attaches middleware
|
|
237
|
+
- **Controller** — handles `req`/`res`, calls service, passes errors to `next()`
|
|
238
|
+
- **Service** — all Prisma calls live here, returns plain data, throws on error
|
|
239
|
+
|
|
240
|
+
This is your **template module**. Copy it, rename everything, and replace the Prisma calls for your first real feature.
|
|
241
|
+
|
|
242
|
+
### Auth Module (`src/modules/auth/`) *(if selected)*
|
|
243
|
+
- **Register** — hashes password (bcrypt, 10 rounds), creates user, returns user without password field
|
|
244
|
+
- **Login** — finds user, compares password, signs JWT, returns `{ user, token }`
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Environment Variables
|
|
249
|
+
|
|
250
|
+
Your `.env` is pre-filled when the project is created:
|
|
251
|
+
|
|
252
|
+
```env
|
|
253
|
+
NODE_ENV=development
|
|
254
|
+
PORT=5050
|
|
255
|
+
|
|
256
|
+
# Database — edit to match your Postgres instance
|
|
257
|
+
DATABASE_URL="postgresql://postgres:admin@localhost:5432/my_api_db"
|
|
258
|
+
# Format: postgresql://USER:PASSWORD@HOST:PORT/DATABASE_NAME
|
|
259
|
+
|
|
260
|
+
# JWT — change JWT_SECRET to a long random string before deploying
|
|
261
|
+
JWT_SECRET=your-super-secret-jwt-key-change-this
|
|
262
|
+
JWT_EXPIRES_IN=7d # 7d | 24h | 3600 (seconds)
|
|
263
|
+
|
|
264
|
+
# File Upload (if included)
|
|
265
|
+
MAX_FILE_SIZE=10 # megabytes
|
|
266
|
+
|
|
267
|
+
# CORS
|
|
268
|
+
CLIENT_URL=http://localhost:3000
|
|
269
|
+
SERVER_URL=http://localhost:5050
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
> `.env` is git-ignored. Commit `.env.example` — it shows required keys without real values.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## First-Time Setup (Step by Step)
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# 1. Scaffold
|
|
280
|
+
npx create-xpress-backend
|
|
281
|
+
|
|
282
|
+
# 2. Enter your project
|
|
283
|
+
cd my-api
|
|
284
|
+
|
|
285
|
+
# 3. Install dependencies
|
|
286
|
+
npm install
|
|
287
|
+
|
|
288
|
+
# 4. Create the database (if it doesn't exist yet)
|
|
289
|
+
# psql -U postgres -c "CREATE DATABASE my_api_db;"
|
|
290
|
+
|
|
291
|
+
# 5. Set DATABASE_URL in .env
|
|
292
|
+
# DATABASE_URL="postgresql://postgres:yourpassword@localhost:5432/my_api_db"
|
|
293
|
+
|
|
294
|
+
# 6. Push schema to DB + generate Prisma Client
|
|
295
|
+
npm run db:push
|
|
296
|
+
|
|
297
|
+
# 7. (Optional) Seed initial data
|
|
298
|
+
npm run db:seed
|
|
299
|
+
|
|
300
|
+
# 8. Start dev server
|
|
301
|
+
npm run dev
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Visit `http://localhost:5050/health`:
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"status": "OK",
|
|
309
|
+
"message": "API Running ✅",
|
|
310
|
+
"timestamp": "2026-07-09T10:00:00.000Z",
|
|
311
|
+
"uptime": 3.2
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Available Scripts
|
|
318
|
+
|
|
319
|
+
### TypeScript project
|
|
320
|
+
|
|
321
|
+
| Command | Description |
|
|
322
|
+
|---------|-------------|
|
|
323
|
+
| `npm run dev` | Start with `ts-node-dev` (auto-restarts + transpiles on save) |
|
|
324
|
+
| `npm run build` | Compile TypeScript → `dist/` |
|
|
325
|
+
| `npm start` | Run compiled production build (`node dist/server.js`) |
|
|
326
|
+
| `npm run db:push` | Push schema changes + regenerate Prisma Client |
|
|
327
|
+
| `npm run db:generate` | Regenerate Prisma Client without pushing |
|
|
328
|
+
| `npm run db:dev` | `db:push` + `db:generate` in one command |
|
|
329
|
+
| `npm run db:seed` | Run the database seeder |
|
|
330
|
+
| `npm run db:studio` | Open Prisma Studio |
|
|
331
|
+
| `npm run db:reset` | ⚠️ Force-reset database (drops all data) |
|
|
332
|
+
|
|
333
|
+
### JavaScript project
|
|
334
|
+
|
|
335
|
+
Same as above except:
|
|
336
|
+
- `npm run dev` uses `nodemon`
|
|
337
|
+
- No `npm run build` — runs directly
|
|
338
|
+
- `npm start` runs `node src/server.js`
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## API Reference
|
|
343
|
+
|
|
344
|
+
### Health
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
GET /health
|
|
348
|
+
```
|
|
349
|
+
```json
|
|
350
|
+
{ "status": "OK", "message": "API Running ✅", "timestamp": "...", "uptime": 120.4 }
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
### Demo — CRUD (always included)
|
|
356
|
+
|
|
357
|
+
#### Create
|
|
358
|
+
```
|
|
359
|
+
POST /api/demo
|
|
360
|
+
Content-Type: application/json
|
|
361
|
+
|
|
362
|
+
{ "name": "Alice", "email": "alice@example.com" }
|
|
363
|
+
```
|
|
364
|
+
Returns `201` with the created record.
|
|
365
|
+
|
|
366
|
+
#### Get All
|
|
367
|
+
```
|
|
368
|
+
GET /api/demo
|
|
369
|
+
```
|
|
370
|
+
Returns `200` with array of records (newest first).
|
|
371
|
+
|
|
372
|
+
#### Get by ID
|
|
373
|
+
```
|
|
374
|
+
GET /api/demo/:id
|
|
375
|
+
```
|
|
376
|
+
Returns `200` with the record, or `404`.
|
|
377
|
+
|
|
378
|
+
#### Update
|
|
379
|
+
```
|
|
380
|
+
PUT /api/demo/:id
|
|
381
|
+
Content-Type: application/json
|
|
382
|
+
|
|
383
|
+
{ "name": "Alice Updated" }
|
|
384
|
+
```
|
|
385
|
+
Returns `200` with updated record.
|
|
386
|
+
|
|
387
|
+
#### Delete
|
|
388
|
+
```
|
|
389
|
+
DELETE /api/demo/:id
|
|
390
|
+
```
|
|
391
|
+
Returns `200` with `{ "message": "Demo deleted successfully" }`.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
### File Upload *(if selected)*
|
|
396
|
+
|
|
397
|
+
#### Upload
|
|
398
|
+
```
|
|
399
|
+
POST /api/demo/upload
|
|
400
|
+
Content-Type: multipart/form-data
|
|
401
|
+
|
|
402
|
+
field: file
|
|
403
|
+
```
|
|
404
|
+
Returns `201` with the stored file record.
|
|
405
|
+
|
|
406
|
+
#### List
|
|
407
|
+
```
|
|
408
|
+
GET /api/demo/files/all
|
|
409
|
+
```
|
|
410
|
+
Returns `200` with array of file records.
|
|
411
|
+
|
|
412
|
+
#### Delete
|
|
413
|
+
```
|
|
414
|
+
DELETE /api/demo/files/:fileId
|
|
415
|
+
```
|
|
416
|
+
Deletes physical file from disk + database record.
|
|
417
|
+
|
|
418
|
+
**Allowed types:** JPEG, PNG, GIF, PDF, TXT, DOC, DOCX
|
|
419
|
+
**Max size:** `MAX_FILE_SIZE` MB (default 10)
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
### Auth *(if selected)*
|
|
424
|
+
|
|
425
|
+
#### Register
|
|
426
|
+
```
|
|
427
|
+
POST /api/auth/register
|
|
428
|
+
Content-Type: application/json
|
|
429
|
+
|
|
430
|
+
{ "name": "Alice", "email": "alice@example.com", "password": "secret123" }
|
|
431
|
+
```
|
|
432
|
+
Returns `201` with user object (no password field).
|
|
433
|
+
|
|
434
|
+
#### Login
|
|
435
|
+
```
|
|
436
|
+
POST /api/auth/login
|
|
437
|
+
Content-Type: application/json
|
|
438
|
+
|
|
439
|
+
{ "email": "alice@example.com", "password": "secret123" }
|
|
440
|
+
```
|
|
441
|
+
Returns `200` with:
|
|
442
|
+
```json
|
|
443
|
+
{
|
|
444
|
+
"success": true,
|
|
445
|
+
"data": {
|
|
446
|
+
"user": { "id": "...", "name": "Alice", "email": "alice@example.com" },
|
|
447
|
+
"token": "eyJhbGci..."
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Use the token on protected routes:
|
|
453
|
+
```
|
|
454
|
+
Authorization: Bearer eyJhbGci...
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## How the Architecture Works
|
|
460
|
+
|
|
461
|
+
Every request flows through the same layers in the same order:
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
Incoming Request
|
|
465
|
+
│
|
|
466
|
+
▼
|
|
467
|
+
Middleware Stack (app.ts)
|
|
468
|
+
helmet → cors → morgan → body-parser
|
|
469
|
+
│
|
|
470
|
+
▼
|
|
471
|
+
Module Router (src/modules/index.ts)
|
|
472
|
+
/api/demo → demo.routes.ts
|
|
473
|
+
/api/auth → auth.routes.ts
|
|
474
|
+
│
|
|
475
|
+
▼
|
|
476
|
+
Route (*.routes.ts)
|
|
477
|
+
validate(schema) → authMiddleware → controller.method
|
|
478
|
+
│
|
|
479
|
+
▼
|
|
480
|
+
Controller (*.controller.ts)
|
|
481
|
+
calls service → formats JSON → passes errors to next()
|
|
482
|
+
│
|
|
483
|
+
▼
|
|
484
|
+
Service (*.service.ts)
|
|
485
|
+
all Prisma / database logic
|
|
486
|
+
throws typed errors
|
|
487
|
+
│
|
|
488
|
+
▼
|
|
489
|
+
Central Error Handler (app.ts)
|
|
490
|
+
catches everything passed to next(error)
|
|
491
|
+
returns { success: false, message }
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Key rules:**
|
|
495
|
+
- Controllers never touch the database — that belongs in the service
|
|
496
|
+
- Services never touch `req`/`res` — they work with plain data and types
|
|
497
|
+
- Validation runs before the controller — invalid requests are rejected early
|
|
498
|
+
- All errors bubble to one place — the central error handler in `app.ts`
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Adding a New Module
|
|
503
|
+
|
|
504
|
+
Example: adding a `products` module.
|
|
505
|
+
|
|
506
|
+
**1. Add a Prisma model**
|
|
507
|
+
```prisma
|
|
508
|
+
model Product {
|
|
509
|
+
id String @id @default(uuid())
|
|
510
|
+
name String
|
|
511
|
+
price Float
|
|
512
|
+
createdAt DateTime @default(now())
|
|
513
|
+
updatedAt DateTime @updatedAt
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
**2. Push the schema**
|
|
518
|
+
```bash
|
|
519
|
+
npm run db:push
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**3. Create the module folder**
|
|
523
|
+
```
|
|
524
|
+
src/modules/products/
|
|
525
|
+
├── products.validation.ts
|
|
526
|
+
├── products.service.ts
|
|
527
|
+
├── products.controller.ts
|
|
528
|
+
└── products.routes.ts
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
**4. Write the service** (Prisma calls go here)
|
|
532
|
+
```ts
|
|
533
|
+
import prisma from '../../database/prisma';
|
|
534
|
+
|
|
535
|
+
class ProductsService {
|
|
536
|
+
async getAll() {
|
|
537
|
+
return prisma.product.findMany({ orderBy: { createdAt: 'desc' } });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
export default new ProductsService();
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**5. Register the route** in `src/modules/index.ts`
|
|
544
|
+
```ts
|
|
545
|
+
import productsRoutes from './products/products.routes';
|
|
546
|
+
router.use('/products', productsRoutes);
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
`GET /api/products` is now live.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## File Upload System
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
Client → multipart/form-data (field: "file")
|
|
557
|
+
│
|
|
558
|
+
▼
|
|
559
|
+
upload.single('file') ← Multer middleware
|
|
560
|
+
validates type + size
|
|
561
|
+
writes to uploads/<name>-<timestamp>-<random>.<ext>
|
|
562
|
+
attaches to req.file
|
|
563
|
+
│
|
|
564
|
+
▼
|
|
565
|
+
Controller reads req.file
|
|
566
|
+
passes userId + file to service
|
|
567
|
+
│
|
|
568
|
+
▼
|
|
569
|
+
Service creates File record in database
|
|
570
|
+
stores: filename, originalName, path, size, mimetype, userId
|
|
571
|
+
│
|
|
572
|
+
▼
|
|
573
|
+
File on disk: uploads/file-1720524000000-123456789.jpg
|
|
574
|
+
DB record in: files table
|
|
575
|
+
Accessible at: GET /uploads/<filename>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
To **restrict file types**, edit `allowedMimes` in `src/middleware/upload.ts`.
|
|
579
|
+
To **change max size**, update `MAX_FILE_SIZE` in `.env` (value in MB).
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## JWT Auth System
|
|
584
|
+
|
|
585
|
+
```
|
|
586
|
+
POST /api/auth/register
|
|
587
|
+
validate { name, email, password }
|
|
588
|
+
check if email exists → 409 if yes
|
|
589
|
+
bcrypt.hash(password, 10)
|
|
590
|
+
prisma.user.create(...)
|
|
591
|
+
return user (password stripped)
|
|
592
|
+
|
|
593
|
+
POST /api/auth/login
|
|
594
|
+
validate { email, password }
|
|
595
|
+
find user by email → 401 if not found
|
|
596
|
+
bcrypt.compare(...) → 401 if wrong
|
|
597
|
+
jwt.sign({ id, email }, JWT_SECRET, { expiresIn })
|
|
598
|
+
return { user, token }
|
|
599
|
+
|
|
600
|
+
Protected Route
|
|
601
|
+
Authorization: Bearer <token>
|
|
602
|
+
authMiddleware verifies token
|
|
603
|
+
attaches decoded payload → req.user
|
|
604
|
+
→ 401 if missing or invalid
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**To protect a route:**
|
|
608
|
+
```ts
|
|
609
|
+
// TypeScript
|
|
610
|
+
import authMiddleware from '../../middleware/auth';
|
|
611
|
+
router.get('/me', authMiddleware, controller.getMe.bind(controller));
|
|
612
|
+
```
|
|
613
|
+
```js
|
|
614
|
+
// JavaScript
|
|
615
|
+
const authMiddleware = require('../../middleware/auth');
|
|
616
|
+
router.get('/me', authMiddleware, controller.getMe);
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Inside the controller, `req.user` will contain `{ id, email, iat, exp }`.
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## Database Workflows
|
|
624
|
+
|
|
625
|
+
### Iterating on your schema
|
|
626
|
+
|
|
627
|
+
```bash
|
|
628
|
+
# 1. Edit prisma/schema.prisma
|
|
629
|
+
# 2. Push + regenerate
|
|
630
|
+
npm run db:push
|
|
631
|
+
# 3. Browse your data
|
|
632
|
+
npm run db:studio
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### Reset for a clean slate (dev only)
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
npm run db:reset # ⚠️ drops all data
|
|
639
|
+
npm run db:seed # re-seed if needed
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Connecting to a hosted database
|
|
643
|
+
|
|
644
|
+
```env
|
|
645
|
+
# Supabase
|
|
646
|
+
DATABASE_URL="postgresql://postgres:[password]@db.[ref].supabase.co:5432/postgres"
|
|
647
|
+
|
|
648
|
+
# Neon
|
|
649
|
+
DATABASE_URL="postgresql://[user]:[password]@[host].neon.tech/[db]?sslmode=require"
|
|
650
|
+
|
|
651
|
+
# Railway
|
|
652
|
+
DATABASE_URL="postgresql://postgres:[password]@[host].railway.app:5432/railway"
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Deployment Checklist
|
|
658
|
+
|
|
659
|
+
- [ ] `NODE_ENV=production` set in hosting environment
|
|
660
|
+
- [ ] `JWT_SECRET` is a long, random, unique string — never the default
|
|
661
|
+
- [ ] `CLIENT_URL` set to your real frontend domain (not `*`)
|
|
662
|
+
- [ ] `DATABASE_URL` points to production database
|
|
663
|
+
- [ ] `npm run db:push` run against the production database
|
|
664
|
+
- [ ] **TypeScript only:** `npm run build` run before deploy — serve from `dist/`
|
|
665
|
+
- [ ] `uploads/` is on persistent storage (not ephemeral filesystem)
|
|
666
|
+
- [ ] Demo routes removed or protected before go-live
|
|
667
|
+
|
|
668
|
+
**Production start:**
|
|
669
|
+
```bash
|
|
670
|
+
# TypeScript
|
|
671
|
+
npm run build && npm start
|
|
672
|
+
|
|
673
|
+
# JavaScript
|
|
674
|
+
npm start
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
**Minimal Dockerfile (TypeScript):**
|
|
678
|
+
```dockerfile
|
|
679
|
+
FROM node:20-alpine
|
|
680
|
+
WORKDIR /app
|
|
681
|
+
COPY package*.json ./
|
|
682
|
+
RUN npm ci
|
|
683
|
+
COPY . .
|
|
684
|
+
RUN npm run build
|
|
685
|
+
EXPOSE 5050
|
|
686
|
+
CMD ["npm", "start"]
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
## FAQ
|
|
692
|
+
|
|
693
|
+
**Q: Which should I pick — TypeScript or JavaScript?**
|
|
694
|
+
A: TypeScript is the default for a reason. Strict typing catches bugs before runtime, makes refactoring reliable, and improves IDE autocomplete. If you're already comfortable with TS, pick it. If you're learning or need a quick prototype, JS is perfectly fine.
|
|
695
|
+
|
|
696
|
+
**Q: What does `ts-node-dev` do?**
|
|
697
|
+
A: It's the TypeScript equivalent of `nodemon` — it watches `.ts` files, transpiles them on the fly, and restarts the server on changes. No manual `tsc` step during development.
|
|
698
|
+
|
|
699
|
+
**Q: Do I need to run `npm run build` during development?**
|
|
700
|
+
A: No. `npm run dev` uses `ts-node-dev` which runs TypeScript directly. `npm run build` is only needed when preparing for production.
|
|
701
|
+
|
|
702
|
+
**Q: Can I use MySQL or SQLite instead of PostgreSQL?**
|
|
703
|
+
A: The generated project uses `@prisma/adapter-pg` (PostgreSQL-specific). To switch, remove the adapter, change `provider` in `schema.prisma`, and replace the `pg` pool. Prisma supports MySQL, SQLite, MongoDB, and others.
|
|
704
|
+
|
|
705
|
+
**Q: Why is there a `demo` module instead of a real resource?**
|
|
706
|
+
A: It's intentionally generic so it doesn't clash with your actual domain models. It shows the pattern — rename and replace it for your first real feature.
|
|
707
|
+
|
|
708
|
+
**Q: How do I add the `password` field if I didn't choose JWT auth?**
|
|
709
|
+
A: Add it to the `User` model in `schema.prisma`:
|
|
710
|
+
```prisma
|
|
711
|
+
model User {
|
|
712
|
+
...
|
|
713
|
+
password String?
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
Then run `npm run db:push`.
|
|
717
|
+
|
|
718
|
+
**Q: What is `prisma.config.ts` / `prisma.config.js` for?**
|
|
719
|
+
A: Prisma 7 introduced a new config file format. It tells the Prisma CLI where to find `DATABASE_URL`. You don't need to edit it.
|
|
720
|
+
|
|
721
|
+
---
|
|
722
|
+
|
|
723
|
+
## License
|
|
724
|
+
|
|
725
|
+
MIT © [ayushsolanki29](https://github.com/ayushsolanki29/create-xpress-backend)
|