create-charcole 1.1.0 → 2.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/.github/workflows/release.yml +26 -0
- package/CHANGELOG.md +25 -0
- package/README.md +11 -1
- package/bin/index.js +90 -71
- package/bin/lib/pkgManager.js +66 -0
- package/bin/lib/templateHandler.js +70 -0
- package/package.json +4 -1
- package/template/js/basePackage.json +28 -0
- package/template/{package-lock.json → js/package-lock.json} +1253 -1253
- package/template/{package.json → js/package.json} +28 -28
- package/template/ts/.env.example +8 -0
- package/template/ts/ARCHITECTURE_DIAGRAMS.md +283 -0
- package/template/ts/CHECKLIST.md +279 -0
- package/template/ts/COMPLETE.md +405 -0
- package/template/ts/ERROR_HANDLING.md +393 -0
- package/template/ts/IMPLEMENTATION.md +368 -0
- package/template/ts/IMPLEMENTATION_COMPLETE.md +363 -0
- package/template/ts/INDEX.md +290 -0
- package/template/ts/QUICK_REFERENCE.md +270 -0
- package/template/ts/README.md +855 -0
- package/template/ts/basePackage.json +36 -0
- package/template/ts/package-lock.json +2428 -0
- package/template/ts/package.json +32 -0
- package/template/ts/src/app.js +75 -0
- package/template/ts/src/app.ts +66 -0
- package/template/ts/src/config/constants.js +20 -0
- package/template/ts/src/config/constants.ts +27 -0
- package/template/ts/src/config/env.js +26 -0
- package/template/ts/src/config/env.ts +40 -0
- package/template/ts/src/middlewares/errorHandler.js +180 -0
- package/template/ts/src/middlewares/errorHandler.ts +209 -0
- package/template/ts/src/middlewares/requestLogger.js +33 -0
- package/template/ts/src/middlewares/requestLogger.ts +38 -0
- package/template/ts/src/middlewares/validateRequest.js +42 -0
- package/template/ts/src/middlewares/validateRequest.ts +46 -0
- package/template/ts/src/modules/health/controller.js +50 -0
- package/template/ts/src/modules/health/controller.ts +64 -0
- package/template/ts/src/routes.js +17 -0
- package/template/ts/src/routes.ts +16 -0
- package/template/ts/src/server.js +38 -0
- package/template/ts/src/server.ts +42 -0
- package/template/ts/src/types/express.d.ts +9 -0
- package/template/ts/src/utils/AppError.js +182 -0
- package/template/ts/src/utils/AppError.ts +220 -0
- package/template/ts/src/utils/logger.js +73 -0
- package/template/ts/src/utils/logger.ts +55 -0
- package/template/ts/src/utils/response.js +51 -0
- package/template/ts/src/utils/response.ts +100 -0
- package/template/ts/test-api.js +100 -0
- package/template/ts/tsconfig.json +19 -0
- /package/template/{.env.example → js/.env.example} +0 -0
- /package/template/{ARCHITECTURE_DIAGRAMS.md → js/ARCHITECTURE_DIAGRAMS.md} +0 -0
- /package/template/{CHECKLIST.md → js/CHECKLIST.md} +0 -0
- /package/template/{COMPLETE.md → js/COMPLETE.md} +0 -0
- /package/template/{ERROR_HANDLING.md → js/ERROR_HANDLING.md} +0 -0
- /package/template/{IMPLEMENTATION.md → js/IMPLEMENTATION.md} +0 -0
- /package/template/{IMPLEMENTATION_COMPLETE.md → js/IMPLEMENTATION_COMPLETE.md} +0 -0
- /package/template/{INDEX.md → js/INDEX.md} +0 -0
- /package/template/{QUICK_REFERENCE.md → js/QUICK_REFERENCE.md} +0 -0
- /package/template/{README.md → js/README.md} +0 -0
- /package/template/{src → js/src}/app.js +0 -0
- /package/template/{src → js/src}/config/constants.js +0 -0
- /package/template/{src → js/src}/config/env.js +0 -0
- /package/template/{src → js/src}/middlewares/errorHandler.js +0 -0
- /package/template/{src → js/src}/middlewares/requestLogger.js +0 -0
- /package/template/{src → js/src}/middlewares/validateRequest.js +0 -0
- /package/template/{src → js/src}/modules/health/controller.js +0 -0
- /package/template/{src → js/src}/routes.js +0 -0
- /package/template/{src → js/src}/server.js +0 -0
- /package/template/{src → js/src}/utils/AppError.js +0 -0
- /package/template/{src → js/src}/utils/logger.js +0 -0
- /package/template/{src → js/src}/utils/response.js +0 -0
- /package/template/{test-api.js → js/test-api.js} +0 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
# Getting Started with Charcole API
|
|
2
|
+
|
|
3
|
+
Welcome! This guide will help you set up and start using the Charcole API framework.
|
|
4
|
+
|
|
5
|
+
## 📚 Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Installation](#installation)
|
|
8
|
+
2. [Project Structure](#project-structure)
|
|
9
|
+
3. [Configuration](#configuration)
|
|
10
|
+
4. [Creating Your First Endpoint](#creating-your-first-endpoint)
|
|
11
|
+
5. [Error Handling](#error-handling)
|
|
12
|
+
6. [Validation](#validation)
|
|
13
|
+
7. [Logging](#logging)
|
|
14
|
+
8. [Running Your API](#running-your-api)
|
|
15
|
+
9. [Troubleshooting](#troubleshooting)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🔧 Installation
|
|
20
|
+
|
|
21
|
+
### Prerequisites
|
|
22
|
+
|
|
23
|
+
- Node.js 18+ ([Download](https://nodejs.org/))
|
|
24
|
+
- npm or yarn
|
|
25
|
+
|
|
26
|
+
### Setup Steps
|
|
27
|
+
|
|
28
|
+
1. **Create Charcole App**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx create-charcole@latest charcole-demo
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Create environment file**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cp .env.example .env
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. **Run the charcole**
|
|
41
|
+
```bash
|
|
42
|
+
npm run dev
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You should see:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
[2024-01-20T12:00:00.000Z] INFO: Express app configured successfully
|
|
49
|
+
[2024-01-20T12:00:00.000Z] INFO: 🔥 Server running in development mode
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📂 Project Structure
|
|
55
|
+
|
|
56
|
+
Understanding the folder structure:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
charcole-demo/
|
|
60
|
+
├── src/ # All application code
|
|
61
|
+
│ ├── config/
|
|
62
|
+
│ │ ├── env.js # Environment validation
|
|
63
|
+
│ │ │ # Validates all .env variables at startup
|
|
64
|
+
│ │ │ # If invalid, server won't start
|
|
65
|
+
│ │ │
|
|
66
|
+
│ │ └── constants.js # HTTP status codes & error messages
|
|
67
|
+
│ │ # Use these in your code
|
|
68
|
+
│ │
|
|
69
|
+
│ ├── middlewares/ # Express middleware
|
|
70
|
+
│ │ ├── errorHandler.js # ⭐ IMPORTANT: Global error handler
|
|
71
|
+
│ │ │ # Every error flows through here
|
|
72
|
+
│ │ │ # Also exports: asyncHandler, AppError classes
|
|
73
|
+
│ │ │
|
|
74
|
+
│ │ ├── validateRequest.js # Validates request body, query, params
|
|
75
|
+
│ │ │ # Uses Zod schemas
|
|
76
|
+
│ │ │ # Throws ValidationError if invalid
|
|
77
|
+
│ │ │
|
|
78
|
+
│ │ └── requestLogger.js # Logs all incoming requests
|
|
79
|
+
│ │ # Logs: method, path, status, duration, IP
|
|
80
|
+
│ │
|
|
81
|
+
│ ├── modules/ # Feature modules (organized by feature)
|
|
82
|
+
│ │ └── health/
|
|
83
|
+
│ │ ├── controller.js # Route handlers for this feature
|
|
84
|
+
│ │ │ # Export: getHealth, createItem, etc.
|
|
85
|
+
│ │ │ # Export: validation schemas
|
|
86
|
+
│ │ │
|
|
87
|
+
│ │ └── service.js # (Optional) Business logic
|
|
88
|
+
│ │ └── model.js # (Optional) Data models
|
|
89
|
+
│ │
|
|
90
|
+
│ ├── utils/
|
|
91
|
+
│ │ ├── AppError.js # ⭐ IMPORTANT: Error class hierarchy
|
|
92
|
+
│ │ │ # Use these to throw errors
|
|
93
|
+
│ │ │ # ValidationError, NotFoundError, etc.
|
|
94
|
+
│ │ │
|
|
95
|
+
│ │ ├── logger.js # Structured logging
|
|
96
|
+
│ │ │ # Use: logger.info(), logger.error(), etc.
|
|
97
|
+
│ │ │
|
|
98
|
+
│ │ └── response.js # Response helpers
|
|
99
|
+
│ │ # Use: sendSuccess() for responses
|
|
100
|
+
│ │
|
|
101
|
+
│ ├── app.js # Express app configuration
|
|
102
|
+
│ │ # All middleware setup
|
|
103
|
+
│ │ # Error handler registered here (last)
|
|
104
|
+
│ │
|
|
105
|
+
│ ├── routes.js # All API routes
|
|
106
|
+
│ │ # Import handlers from modules
|
|
107
|
+
│ │ # Define routes here
|
|
108
|
+
│ │
|
|
109
|
+
│ └── server.js # Server entry point
|
|
110
|
+
│ # Listen on PORT
|
|
111
|
+
│ # Graceful shutdown handling
|
|
112
|
+
│
|
|
113
|
+
├── .env # Environment variables (GITIGNORED)
|
|
114
|
+
├── .env.example # Example env variables (committed)
|
|
115
|
+
├── package.json # Dependencies & scripts
|
|
116
|
+
├── README.md # Project overview (for GitHub/npm)
|
|
117
|
+
└── template/README.md # This file - Getting started guide
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Creating New Modules
|
|
121
|
+
|
|
122
|
+
Create a new feature:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Create directory
|
|
126
|
+
mkdir -p src/modules/users
|
|
127
|
+
|
|
128
|
+
# Create files
|
|
129
|
+
touch src/modules/users/controller.js
|
|
130
|
+
touch src/modules/users/service.js
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**controller.js** - Route handlers
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
import { asyncHandler } from "../../middlewares/errorHandler.js";
|
|
137
|
+
import { sendSuccess } from "../../utils/response.js";
|
|
138
|
+
import { z } from "zod";
|
|
139
|
+
|
|
140
|
+
export const getUserSchema = z.object({
|
|
141
|
+
params: z.object({ id: z.string() }),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
export const getUser = asyncHandler(async (req, res) => {
|
|
145
|
+
const { id } = req.params;
|
|
146
|
+
const user = await findUserById(id);
|
|
147
|
+
if (!user) throw new NotFoundError("User");
|
|
148
|
+
sendSuccess(res, user);
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## ⚙️ Configuration
|
|
155
|
+
|
|
156
|
+
### Environment Variables
|
|
157
|
+
|
|
158
|
+
Edit `.env`:
|
|
159
|
+
|
|
160
|
+
```env
|
|
161
|
+
# Server
|
|
162
|
+
NODE_ENV=development # development, production, or test
|
|
163
|
+
PORT=3000 # Server port
|
|
164
|
+
|
|
165
|
+
# Logging
|
|
166
|
+
LOG_LEVEL=info # debug, info, warn, error
|
|
167
|
+
# In production, use warn or error
|
|
168
|
+
|
|
169
|
+
# CORS
|
|
170
|
+
CORS_ORIGIN=* # Change to your domain in production
|
|
171
|
+
# Example: https://myapp.com
|
|
172
|
+
|
|
173
|
+
# Timeouts
|
|
174
|
+
REQUEST_TIMEOUT=30000 # 30 seconds
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Using Environment Variables
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
import { env } from "./config/env.js";
|
|
181
|
+
|
|
182
|
+
console.log(env.PORT); // 3000
|
|
183
|
+
console.log(env.NODE_ENV); // development
|
|
184
|
+
console.log(env.isProduction); // false
|
|
185
|
+
console.log(env.isDevelopment); // true
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 🚀 Creating Your First Endpoint
|
|
191
|
+
|
|
192
|
+
### Step 1: Create Controller
|
|
193
|
+
|
|
194
|
+
**src/modules/posts/controller.js**
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
import { z } from "zod";
|
|
198
|
+
import { asyncHandler } from "../../middlewares/errorHandler.js";
|
|
199
|
+
import { validateRequest } from "../../middlewares/validateRequest.js";
|
|
200
|
+
import { sendSuccess } from "../../utils/response.js";
|
|
201
|
+
import { NotFoundError } from "../../middlewares/errorHandler.js";
|
|
202
|
+
|
|
203
|
+
// Define validation schema
|
|
204
|
+
export const createPostSchema = z.object({
|
|
205
|
+
body: z.object({
|
|
206
|
+
title: z.string().min(1, "Title required").max(200),
|
|
207
|
+
content: z.string().min(1, "Content required"),
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Define handler
|
|
212
|
+
export const createPost = asyncHandler(async (req, res) => {
|
|
213
|
+
const { title, content } = req.validatedData.body;
|
|
214
|
+
|
|
215
|
+
// Your logic here
|
|
216
|
+
const post = {
|
|
217
|
+
id: "1",
|
|
218
|
+
title,
|
|
219
|
+
content,
|
|
220
|
+
createdAt: new Date(),
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
sendSuccess(res, post, 201, "Post created successfully");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
export const getPost = asyncHandler(async (req, res) => {
|
|
227
|
+
const { id } = req.params;
|
|
228
|
+
|
|
229
|
+
// Simulate database fetch
|
|
230
|
+
if (id !== "1") {
|
|
231
|
+
throw new NotFoundError("Post", { id });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const post = { id: "1", title: "Hello", content: "World" };
|
|
235
|
+
sendSuccess(res, post);
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Step 2: Register Routes
|
|
240
|
+
|
|
241
|
+
**src/routes.js**
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
import { Router } from "express";
|
|
245
|
+
import { getHealth, createItem } from "./modules/health/controller.js";
|
|
246
|
+
import {
|
|
247
|
+
createPost,
|
|
248
|
+
getPost,
|
|
249
|
+
createPostSchema,
|
|
250
|
+
} from "./modules/posts/controller.js";
|
|
251
|
+
import { validateRequest } from "./middlewares/validateRequest.js";
|
|
252
|
+
|
|
253
|
+
const router = Router();
|
|
254
|
+
|
|
255
|
+
// Health check
|
|
256
|
+
router.get("/health", getHealth);
|
|
257
|
+
|
|
258
|
+
// Posts
|
|
259
|
+
router.post("/posts", validateRequest(createPostSchema), createPost);
|
|
260
|
+
router.get("/posts/:id", getPost);
|
|
261
|
+
|
|
262
|
+
export default router;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Step 3: Test Your Endpoint
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Start server
|
|
269
|
+
npm run dev
|
|
270
|
+
|
|
271
|
+
# Test creation
|
|
272
|
+
curl -X POST http://localhost:3000/api/posts \
|
|
273
|
+
-H "Content-Type: application/json" \
|
|
274
|
+
-d '{"title":"My Post","content":"Hello World"}'
|
|
275
|
+
|
|
276
|
+
# Test retrieval
|
|
277
|
+
curl http://localhost:3000/api/posts/1
|
|
278
|
+
|
|
279
|
+
# Test 404
|
|
280
|
+
curl http://localhost:3000/api/posts/999
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## 🛡️ Error Handling
|
|
286
|
+
|
|
287
|
+
### Understanding Errors
|
|
288
|
+
|
|
289
|
+
There are **two types of errors**:
|
|
290
|
+
|
|
291
|
+
#### 1. Operational Errors (Expected)
|
|
292
|
+
|
|
293
|
+
User/input errors that can be handled gracefully.
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
import {
|
|
297
|
+
ValidationError, // 422 - Input validation failed
|
|
298
|
+
BadRequestError, // 400 - Malformed request
|
|
299
|
+
AuthenticationError, // 401 - Invalid credentials
|
|
300
|
+
AuthorizationError, // 403 - Permission denied
|
|
301
|
+
NotFoundError, // 404 - Resource not found
|
|
302
|
+
ConflictError, // 409 - Duplicate/conflict
|
|
303
|
+
AppError, // Generic error
|
|
304
|
+
} from "./middlewares/errorHandler.js";
|
|
305
|
+
|
|
306
|
+
// Throw operational errors
|
|
307
|
+
throw new NotFoundError("User", { id: userId });
|
|
308
|
+
throw new ConflictError("Email already exists");
|
|
309
|
+
throw new AuthenticationError("Invalid password");
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### 2. Programmer Errors (Bugs)
|
|
313
|
+
|
|
314
|
+
Unexpected errors that indicate code issues.
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
// These are automatically caught and handled:
|
|
318
|
+
// - TypeError
|
|
319
|
+
// - ReferenceError
|
|
320
|
+
// - SyntaxError
|
|
321
|
+
// - Any unhandled error
|
|
322
|
+
|
|
323
|
+
// Example: This is caught automatically
|
|
324
|
+
const user = null;
|
|
325
|
+
user.name; // TypeError: Cannot read property 'name' of null
|
|
326
|
+
// → Logged as ERROR with stack trace
|
|
327
|
+
// → Generic response sent to client
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Using asyncHandler
|
|
331
|
+
|
|
332
|
+
**ALWAYS wrap async handlers** to catch errors:
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
// ✅ CORRECT - Error is caught
|
|
336
|
+
router.get(
|
|
337
|
+
"/users/:id",
|
|
338
|
+
asyncHandler(async (req, res) => {
|
|
339
|
+
const user = await User.findById(req.params.id); // Error caught
|
|
340
|
+
if (!user) throw new NotFoundError("User");
|
|
341
|
+
sendSuccess(res, user);
|
|
342
|
+
}),
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// ❌ WRONG - Error NOT caught!
|
|
346
|
+
router.get("/users/:id", async (req, res) => {
|
|
347
|
+
const user = await User.findById(req.params.id); // Error leaks!
|
|
348
|
+
sendSuccess(res, user);
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Response Format
|
|
353
|
+
|
|
354
|
+
**Success (200):**
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"success": true,
|
|
359
|
+
"message": "User created successfully",
|
|
360
|
+
"data": { "id": "123", "name": "John" },
|
|
361
|
+
"timestamp": "2024-01-20T12:00:00.000Z"
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Validation Error (422):**
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"success": false,
|
|
370
|
+
"message": "Validation failed",
|
|
371
|
+
"code": "VALIDATION_ERROR",
|
|
372
|
+
"statusCode": 422,
|
|
373
|
+
"errors": [
|
|
374
|
+
{
|
|
375
|
+
"field": "email",
|
|
376
|
+
"message": "Invalid email address",
|
|
377
|
+
"code": "invalid_email"
|
|
378
|
+
}
|
|
379
|
+
],
|
|
380
|
+
"timestamp": "2024-01-20T12:00:00.000Z"
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Not Found (404):**
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{
|
|
388
|
+
"success": false,
|
|
389
|
+
"message": "User not found",
|
|
390
|
+
"code": "NOT_FOUND",
|
|
391
|
+
"statusCode": 404,
|
|
392
|
+
"context": { "id": "999" },
|
|
393
|
+
"timestamp": "2024-01-20T12:00:00.000Z"
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## ✔️ Validation
|
|
400
|
+
|
|
401
|
+
### Zod Schema Basics
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
import { z } from "zod";
|
|
405
|
+
|
|
406
|
+
// Define schema
|
|
407
|
+
const userSchema = z.object({
|
|
408
|
+
body: z.object({
|
|
409
|
+
email: z.string().email("Invalid email"),
|
|
410
|
+
name: z.string().min(1, "Name required"),
|
|
411
|
+
age: z.number().min(18, "Must be 18+").optional(),
|
|
412
|
+
}),
|
|
413
|
+
query: z.object({
|
|
414
|
+
page: z.coerce.number().default(1),
|
|
415
|
+
}),
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Use in route
|
|
419
|
+
router.post("/users", validateRequest(userSchema), handler);
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Common Validations
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
// String
|
|
426
|
+
z.string().min(1, "Required");
|
|
427
|
+
z.string().email("Invalid email");
|
|
428
|
+
z.string().url("Invalid URL");
|
|
429
|
+
z.string().regex(/^\d+$/, "Numbers only");
|
|
430
|
+
|
|
431
|
+
// Number
|
|
432
|
+
z.number().min(0).max(100);
|
|
433
|
+
z.coerce.number(); // Convert string to number
|
|
434
|
+
|
|
435
|
+
// Array
|
|
436
|
+
z.array(z.string());
|
|
437
|
+
z.array(z.object({ id: z.string() }));
|
|
438
|
+
|
|
439
|
+
// Object
|
|
440
|
+
z.object({ key: z.string() });
|
|
441
|
+
|
|
442
|
+
// Union
|
|
443
|
+
z.union([z.string(), z.number()]);
|
|
444
|
+
|
|
445
|
+
// Optional
|
|
446
|
+
z.string().optional();
|
|
447
|
+
z.string().default("value");
|
|
448
|
+
|
|
449
|
+
// Enum
|
|
450
|
+
z.enum(["active", "inactive"]);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 📝 Logging
|
|
456
|
+
|
|
457
|
+
### Using Logger
|
|
458
|
+
|
|
459
|
+
```javascript
|
|
460
|
+
import { logger } from "./utils/logger.js";
|
|
461
|
+
|
|
462
|
+
// Different log levels
|
|
463
|
+
logger.debug("Detailed debug info", { data: true });
|
|
464
|
+
logger.info("Important information", { userId: 123 });
|
|
465
|
+
logger.warn("Warning - something unexpected", { statusCode: 404 });
|
|
466
|
+
logger.error("Error occurred", { error: "message" }, stack);
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Log Output
|
|
470
|
+
|
|
471
|
+
**Development** (colorized):
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
[2024-01-20T12:00:00.000Z] DEBUG: Debug message
|
|
475
|
+
[2024-01-20T12:00:00.000Z] INFO: Request processed
|
|
476
|
+
[2024-01-20T12:00:00.000Z] WARN: User not found
|
|
477
|
+
[2024-01-20T12:00:00.000Z] ERROR: Database error
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Production** (to file/service):
|
|
481
|
+
|
|
482
|
+
```
|
|
483
|
+
{"level":"info","message":"Request processed","timestamp":"2024-01-20T12:00:00.000Z"}
|
|
484
|
+
{"level":"error","message":"Database error","timestamp":"2024-01-20T12:00:00.000Z"}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## 🎯 Running Your API
|
|
490
|
+
|
|
491
|
+
### Development
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
# Start with auto-reload
|
|
495
|
+
npm run dev
|
|
496
|
+
|
|
497
|
+
# Output:
|
|
498
|
+
# [2024-01-20T12:00:00.000Z] INFO: Express app configured successfully
|
|
499
|
+
# [2024-01-20T12:00:00.000Z] INFO: 🔥 Server running in development mode
|
|
500
|
+
# { "url": "http://localhost:3000", "port": 3000 }
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Production
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
# Set environment
|
|
507
|
+
export NODE_ENV=production
|
|
508
|
+
|
|
509
|
+
# Start server
|
|
510
|
+
npm start
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Testing
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
# Test API endpoints
|
|
517
|
+
node test-api.js
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 🆘 Troubleshooting
|
|
523
|
+
|
|
524
|
+
### Server won't start
|
|
525
|
+
|
|
526
|
+
**Error:** `listen EADDRINUSE: address already in use :::3000`
|
|
527
|
+
|
|
528
|
+
**Solution:**
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
# Kill process on port 3000
|
|
532
|
+
# macOS/Linux:
|
|
533
|
+
lsof -ti:3000 | xargs kill -9
|
|
534
|
+
|
|
535
|
+
# Windows PowerShell:
|
|
536
|
+
Get-Process | Where-Object {$_.Port -eq 3000} | Stop-Process -Force
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### Validation errors not working
|
|
540
|
+
|
|
541
|
+
**Ensure you:**
|
|
542
|
+
|
|
543
|
+
1. Import `validateRequest` from middlewares
|
|
544
|
+
2. Add it as middleware before handler
|
|
545
|
+
3. Pass schema with body/query/params structure
|
|
546
|
+
4. Use `req.validatedData` in handler
|
|
547
|
+
|
|
548
|
+
```javascript
|
|
549
|
+
// ✅ Correct
|
|
550
|
+
const schema = z.object({
|
|
551
|
+
body: z.object({ name: z.string() }),
|
|
552
|
+
});
|
|
553
|
+
router.post("/items", validateRequest(schema), handler);
|
|
554
|
+
|
|
555
|
+
// In handler:
|
|
556
|
+
const { name } = req.validatedData.body;
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Errors not being caught
|
|
560
|
+
|
|
561
|
+
**Ensure you:**
|
|
562
|
+
|
|
563
|
+
1. Wrap handler with `asyncHandler`
|
|
564
|
+
2. Throw AppError instances
|
|
565
|
+
3. Don't try-catch to return res.status()
|
|
566
|
+
|
|
567
|
+
```javascript
|
|
568
|
+
// ✅ Correct
|
|
569
|
+
router.get(
|
|
570
|
+
"/items/:id",
|
|
571
|
+
asyncHandler(async (req, res) => {
|
|
572
|
+
const item = await Item.findById(req.params.id);
|
|
573
|
+
if (!item) throw new NotFoundError("Item");
|
|
574
|
+
sendSuccess(res, item);
|
|
575
|
+
}),
|
|
576
|
+
);
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Environment variables not loading
|
|
580
|
+
|
|
581
|
+
**Check:**
|
|
582
|
+
|
|
583
|
+
1. `.env` file exists in root
|
|
584
|
+
2. Variable names match (case-sensitive)
|
|
585
|
+
3. Restart server after changing .env
|
|
586
|
+
4. Use `env.VARIABLE_NAME` to access
|
|
587
|
+
|
|
588
|
+
```javascript
|
|
589
|
+
import { env } from "./config/env.js";
|
|
590
|
+
|
|
591
|
+
console.log(env.PORT); // Will be validated at startup
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### CORS errors
|
|
595
|
+
|
|
596
|
+
**In development**, CORS should be `*`:
|
|
597
|
+
|
|
598
|
+
```env
|
|
599
|
+
CORS_ORIGIN=*
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**In production**, set your domain:
|
|
603
|
+
|
|
604
|
+
```env
|
|
605
|
+
CORS_ORIGIN=https://myapp.com
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## 📖 Next Steps
|
|
611
|
+
|
|
612
|
+
1. **Read:** [Main README](../README.md) - Project overview
|
|
613
|
+
2. **Learn:** [Quick Reference](../QUICK_REFERENCE.md) - Patterns & rules
|
|
614
|
+
3. **Deep Dive:** [Error Handling Guide](../ERROR_HANDLING.md) - Full documentation
|
|
615
|
+
4. **Build:** Create your first module using examples above
|
|
616
|
+
5. **Deploy:** Follow production checklist in README
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## 🤔 Common Patterns
|
|
621
|
+
|
|
622
|
+
### Create with Validation
|
|
623
|
+
|
|
624
|
+
```javascript
|
|
625
|
+
const createSchema = z.object({
|
|
626
|
+
body: z.object({
|
|
627
|
+
name: z.string().min(1),
|
|
628
|
+
email: z.string().email(),
|
|
629
|
+
}),
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
export const create = asyncHandler(async (req, res) => {
|
|
633
|
+
const { name, email } = req.validatedData.body;
|
|
634
|
+
const item = await Item.create({ name, email });
|
|
635
|
+
sendSuccess(res, item, 201, "Created");
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
router.post("/items", validateRequest(createSchema), create);
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Get with 404 Handling
|
|
642
|
+
|
|
643
|
+
```javascript
|
|
644
|
+
export const getById = asyncHandler(async (req, res) => {
|
|
645
|
+
const item = await Item.findById(req.params.id);
|
|
646
|
+
if (!item) throw new NotFoundError("Item");
|
|
647
|
+
sendSuccess(res, item);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
router.get("/items/:id", getById);
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Update with Validation
|
|
654
|
+
|
|
655
|
+
```javascript
|
|
656
|
+
const updateSchema = z.object({
|
|
657
|
+
params: z.object({ id: z.string() }),
|
|
658
|
+
body: z.object({
|
|
659
|
+
name: z.string().min(1).optional(),
|
|
660
|
+
email: z.string().email().optional(),
|
|
661
|
+
}),
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
export const update = asyncHandler(async (req, res) => {
|
|
665
|
+
const item = await Item.findByIdAndUpdate(
|
|
666
|
+
req.params.id,
|
|
667
|
+
req.validatedData.body,
|
|
668
|
+
);
|
|
669
|
+
if (!item) throw new NotFoundError("Item");
|
|
670
|
+
sendSuccess(res, item, 200, "Updated");
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
router.patch("/items/:id", validateRequest(updateSchema), update);
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### Delete with 404 Handling
|
|
677
|
+
|
|
678
|
+
```javascript
|
|
679
|
+
export const delete = asyncHandler(async (req, res) => {
|
|
680
|
+
const item = await Item.findByIdAndDelete(req.params.id);
|
|
681
|
+
if (!item) throw new NotFoundError("Item");
|
|
682
|
+
sendSuccess(res, { id: item.id }, 200, "Deleted");
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
router.delete("/items/:id", delete);
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
**You're ready to build!** 🚀
|
|
691
|
+
|
|
692
|
+
Questions? Check the [Full Documentation](../ERROR_HANDLING.md).
|
|
693
|
+
PORT=3000 # Server port
|
|
694
|
+
LOG_LEVEL=info # debug, info, warn, error
|
|
695
|
+
CORS_ORIGIN=\* # CORS origin
|
|
696
|
+
REQUEST_TIMEOUT=30000 # Request timeout in ms
|
|
697
|
+
|
|
698
|
+
````
|
|
699
|
+
|
|
700
|
+
## Running
|
|
701
|
+
|
|
702
|
+
**Development** (with auto-reload):
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
npm run dev
|
|
706
|
+
````
|
|
707
|
+
|
|
708
|
+
**Production**:
|
|
709
|
+
|
|
710
|
+
```bash
|
|
711
|
+
npm start
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
## API Endpoints
|
|
715
|
+
|
|
716
|
+
### Health Check
|
|
717
|
+
|
|
718
|
+
```
|
|
719
|
+
GET /health
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Response:
|
|
723
|
+
|
|
724
|
+
```json
|
|
725
|
+
{
|
|
726
|
+
"success": true,
|
|
727
|
+
"message": "Success",
|
|
728
|
+
"data": {
|
|
729
|
+
"status": "healthy",
|
|
730
|
+
"uptime": 42.123,
|
|
731
|
+
"timestamp": "2024-01-19T10:30:00.000Z"
|
|
732
|
+
},
|
|
733
|
+
"timestamp": "2024-01-19T10:30:00.000Z"
|
|
734
|
+
}
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Create Item (Example with Validation)
|
|
738
|
+
|
|
739
|
+
```
|
|
740
|
+
POST /api/items
|
|
741
|
+
Content-Type: application/json
|
|
742
|
+
|
|
743
|
+
{
|
|
744
|
+
"name": "Example Item",
|
|
745
|
+
"description": "Optional description"
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
## Response Format
|
|
750
|
+
|
|
751
|
+
All API responses follow a consistent format:
|
|
752
|
+
|
|
753
|
+
**Success**:
|
|
754
|
+
|
|
755
|
+
```json
|
|
756
|
+
{
|
|
757
|
+
"success": true,
|
|
758
|
+
"message": "Success message",
|
|
759
|
+
"data": {},
|
|
760
|
+
"timestamp": "2024-01-19T10:30:00.000Z"
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
**Error**:
|
|
765
|
+
|
|
766
|
+
```json
|
|
767
|
+
{
|
|
768
|
+
"success": false,
|
|
769
|
+
"message": "Error message",
|
|
770
|
+
"timestamp": "2024-01-19T10:30:00.000Z"
|
|
771
|
+
}
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**Validation Error**:
|
|
775
|
+
|
|
776
|
+
```json
|
|
777
|
+
{
|
|
778
|
+
"success": false,
|
|
779
|
+
"message": "Validation failed",
|
|
780
|
+
"errors": [
|
|
781
|
+
{
|
|
782
|
+
"field": "name",
|
|
783
|
+
"message": "Name is required",
|
|
784
|
+
"code": "too_small"
|
|
785
|
+
}
|
|
786
|
+
],
|
|
787
|
+
"timestamp": "2024-01-19T10:30:00.000Z"
|
|
788
|
+
}
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
## Creating New Endpoints
|
|
792
|
+
|
|
793
|
+
1. Create controller in `src/modules/<feature>/controller.js`:
|
|
794
|
+
|
|
795
|
+
```javascript
|
|
796
|
+
import { z } from "zod";
|
|
797
|
+
import { sendSuccess } from "../../utils/response.js";
|
|
798
|
+
|
|
799
|
+
export const myHandlerSchema = z.object({
|
|
800
|
+
body: z.object({
|
|
801
|
+
name: z.string().min(1),
|
|
802
|
+
}),
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
export const myHandler = (req, res) => {
|
|
806
|
+
const { name } = req.validatedData.body;
|
|
807
|
+
sendSuccess(res, { name }, 200, "Success");
|
|
808
|
+
};
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
2. Add route in `src/routes.js`:
|
|
812
|
+
|
|
813
|
+
```javascript
|
|
814
|
+
import { myHandler, myHandlerSchema } from "./modules/feature/controller.js";
|
|
815
|
+
|
|
816
|
+
router.post("/feature", validateRequest(myHandlerSchema), myHandler);
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
## Error Handling
|
|
820
|
+
|
|
821
|
+
The app includes comprehensive error handling:
|
|
822
|
+
|
|
823
|
+
- **Zod Validation Errors** - Automatically formatted with field-level errors
|
|
824
|
+
- **Custom Errors** - Use `AppError` for application-specific errors
|
|
825
|
+
- **Unhandled Rejections** - Caught and logged, then process exits
|
|
826
|
+
- **Uncaught Exceptions** - Caught and logged, then process exits
|
|
827
|
+
|
|
828
|
+
## Logging
|
|
829
|
+
|
|
830
|
+
Use the logger throughout your code:
|
|
831
|
+
|
|
832
|
+
```javascript
|
|
833
|
+
import { logger } from "./utils/logger.js";
|
|
834
|
+
|
|
835
|
+
logger.debug("Debug message", { data: true });
|
|
836
|
+
logger.info("Info message", { data: true });
|
|
837
|
+
logger.warn("Warning message", { data: true });
|
|
838
|
+
logger.error("Error message", { data: true });
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
## Production Checklist
|
|
842
|
+
|
|
843
|
+
- [ ] Set `NODE_ENV=production`
|
|
844
|
+
- [ ] Configure `CORS_ORIGIN` for your domain
|
|
845
|
+
- [ ] Set appropriate `LOG_LEVEL`
|
|
846
|
+
- [ ] Add database connection
|
|
847
|
+
- [ ] Implement authentication middleware
|
|
848
|
+
- [ ] Add rate limiting
|
|
849
|
+
- [ ] Add input sanitization
|
|
850
|
+
- [ ] Set up monitoring
|
|
851
|
+
- [ ] Configure reverse proxy (nginx/apache)
|
|
852
|
+
|
|
853
|
+
## License
|
|
854
|
+
|
|
855
|
+
ISC
|