postgresdk 0.1.1-alpha.1 → 0.1.2-alpha.0
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 +549 -8
- package/dist/cli.js +337 -4777
- package/dist/emit-auth.d.ts +2 -0
- package/dist/emit-routes.d.ts +1 -0
- package/dist/emit-server-index.d.ts +5 -0
- package/dist/index.js +333 -4772
- package/dist/types.d.ts +20 -0
- package/package.json +5 -4
package/README.md
CHANGED
@@ -1,6 +1,135 @@
|
|
1
1
|
# postgresdk
|
2
2
|
|
3
|
-
|
3
|
+
Turn your PostgreSQL database into a fully-typed, production-ready SDK in seconds.
|
4
|
+
|
5
|
+
## What You Get
|
6
|
+
|
7
|
+
Start with your existing PostgreSQL database:
|
8
|
+
|
9
|
+
```sql
|
10
|
+
CREATE TABLE authors (
|
11
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
12
|
+
name TEXT NOT NULL,
|
13
|
+
bio TEXT
|
14
|
+
);
|
15
|
+
|
16
|
+
CREATE TABLE books (
|
17
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
18
|
+
title TEXT NOT NULL,
|
19
|
+
author_id UUID REFERENCES authors(id),
|
20
|
+
published_at TIMESTAMPTZ
|
21
|
+
);
|
22
|
+
|
23
|
+
CREATE TABLE tags (
|
24
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
25
|
+
name TEXT UNIQUE NOT NULL
|
26
|
+
);
|
27
|
+
|
28
|
+
CREATE TABLE book_tags (
|
29
|
+
book_id UUID REFERENCES books(id),
|
30
|
+
tag_id UUID REFERENCES tags(id),
|
31
|
+
PRIMARY KEY (book_id, tag_id)
|
32
|
+
);
|
33
|
+
```
|
34
|
+
|
35
|
+
Run one command:
|
36
|
+
|
37
|
+
```bash
|
38
|
+
npx postgresdk
|
39
|
+
```
|
40
|
+
|
41
|
+
Get a complete, type-safe SDK with:
|
42
|
+
|
43
|
+
### 🎯 Client SDK with Full TypeScript Support
|
44
|
+
|
45
|
+
```typescript
|
46
|
+
import { SDK } from "./generated/client";
|
47
|
+
|
48
|
+
const sdk = new SDK({
|
49
|
+
baseUrl: "http://localhost:3000",
|
50
|
+
auth: { apiKey: "your-key" } // Optional auth
|
51
|
+
});
|
52
|
+
|
53
|
+
// ✅ Fully typed - autocomplete everything!
|
54
|
+
const author = await sdk.authors.create({
|
55
|
+
name: "Jane Austen",
|
56
|
+
bio: "English novelist known for social commentary"
|
57
|
+
});
|
58
|
+
|
59
|
+
// ✅ Type-safe relationships with eager loading
|
60
|
+
const booksWithAuthor = await sdk.books.list({
|
61
|
+
include: {
|
62
|
+
author: true, // 1:N relationship
|
63
|
+
tags: true // M:N relationship
|
64
|
+
}
|
65
|
+
});
|
66
|
+
|
67
|
+
// ✅ Complex nested queries
|
68
|
+
const authorsWithEverything = await sdk.authors.list({
|
69
|
+
include: {
|
70
|
+
books: {
|
71
|
+
include: {
|
72
|
+
tags: true
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
});
|
77
|
+
|
78
|
+
// ✅ Built-in pagination & filtering
|
79
|
+
const recentBooks = await sdk.books.list({
|
80
|
+
where: { published_at: { gte: "2024-01-01" } },
|
81
|
+
orderBy: "published_at",
|
82
|
+
order: "desc",
|
83
|
+
limit: 10
|
84
|
+
});
|
85
|
+
```
|
86
|
+
|
87
|
+
### 🚀 Production-Ready REST API
|
88
|
+
|
89
|
+
```typescript
|
90
|
+
import { Hono } from "hono";
|
91
|
+
import { Client } from "pg";
|
92
|
+
import { createRouter } from "./generated/server";
|
93
|
+
|
94
|
+
const app = new Hono();
|
95
|
+
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
96
|
+
await pg.connect();
|
97
|
+
|
98
|
+
// That's it! Full REST API with:
|
99
|
+
// - Input validation (Zod)
|
100
|
+
// - Error handling
|
101
|
+
// - Relationship loading
|
102
|
+
// - Auth middleware (if configured)
|
103
|
+
// - Type safety throughout
|
104
|
+
|
105
|
+
const api = createRouter({ pg });
|
106
|
+
app.route("/", api);
|
107
|
+
|
108
|
+
// GET /v1/authors
|
109
|
+
// POST /v1/authors
|
110
|
+
// GET /v1/authors/:id
|
111
|
+
// PATCH /v1/authors/:id
|
112
|
+
// DELETE /v1/authors/:id
|
113
|
+
// POST /v1/authors/list (with filtering & includes)
|
114
|
+
```
|
115
|
+
|
116
|
+
### 🔒 Type Safety Everywhere
|
117
|
+
|
118
|
+
```typescript
|
119
|
+
// TypeScript catches errors at compile time
|
120
|
+
const book = await sdk.books.create({
|
121
|
+
title: "Pride and Prejudice",
|
122
|
+
author_id: "not-a-uuid", // ❌ Type error!
|
123
|
+
published_at: "invalid-date" // ❌ Type error!
|
124
|
+
});
|
125
|
+
|
126
|
+
// Generated Zod schemas for runtime validation
|
127
|
+
import { InsertBooksSchema } from "./generated/server/zod/books";
|
128
|
+
|
129
|
+
const validated = InsertBooksSchema.parse(requestBody);
|
130
|
+
```
|
131
|
+
|
132
|
+
All from your existing database schema. No manual coding required.
|
4
133
|
|
5
134
|
## Features
|
6
135
|
|
@@ -8,6 +137,7 @@ Generate a fully-typed, production-ready SDK from your PostgreSQL database schem
|
|
8
137
|
- 🔒 **Type Safety** - Full TypeScript types derived from your database schema
|
9
138
|
- ✅ **Runtime Validation** - Zod schemas for request/response validation
|
10
139
|
- 🔗 **Smart Relationships** - Automatic handling of 1:N and M:N relationships with eager loading
|
140
|
+
- 🔐 **Built-in Auth** - API key and JWT authentication with zero configuration
|
11
141
|
- 🎯 **Zero Config** - Works out of the box with sensible defaults
|
12
142
|
- 🏗️ **Framework Ready** - Server routes built for Hono, client SDK works anywhere
|
13
143
|
- 📦 **Lightweight** - Minimal dependencies, optimized for production
|
@@ -72,7 +202,7 @@ Create a `postgresdk.config.ts` file in your project root:
|
|
72
202
|
```typescript
|
73
203
|
export default {
|
74
204
|
// Required
|
75
|
-
connectionString: "postgres://user:pass@localhost:5432/dbname",
|
205
|
+
connectionString: process.env.DATABASE_URL || "postgres://user:pass@localhost:5432/dbname",
|
76
206
|
|
77
207
|
// Optional (with defaults)
|
78
208
|
schema: "public", // Database schema to introspect
|
@@ -81,9 +211,27 @@ export default {
|
|
81
211
|
softDeleteColumn: null, // Column name for soft deletes (e.g., "deleted_at")
|
82
212
|
includeDepthLimit: 3, // Max depth for nested includes
|
83
213
|
dateType: "date", // "date" | "string" - How to handle timestamps
|
214
|
+
|
215
|
+
// Authentication (optional)
|
216
|
+
auth: {
|
217
|
+
strategy: "none" | "api-key" | "jwt-hs256", // Default: "none"
|
218
|
+
|
219
|
+
// For API key auth
|
220
|
+
apiKeyHeader: "x-api-key", // Header name for API key
|
221
|
+
apiKeys: ["key1", "key2"], // Array of valid keys
|
222
|
+
|
223
|
+
// For JWT auth (HS256)
|
224
|
+
jwt: {
|
225
|
+
sharedSecret: "your-secret", // Shared secret for HS256
|
226
|
+
issuer: "your-app", // Optional: validate issuer claim
|
227
|
+
audience: "your-audience" // Optional: validate audience claim
|
228
|
+
}
|
229
|
+
}
|
84
230
|
};
|
85
231
|
```
|
86
232
|
|
233
|
+
Environment variables work directly in the config file - no function wrapper needed.
|
234
|
+
|
87
235
|
## Generated SDK Features
|
88
236
|
|
89
237
|
### CRUD Operations
|
@@ -157,29 +305,421 @@ const user = await sdk.users.create({
|
|
157
305
|
});
|
158
306
|
```
|
159
307
|
|
160
|
-
##
|
308
|
+
## Authentication
|
309
|
+
|
310
|
+
postgresdk supports three authentication strategies out of the box:
|
161
311
|
|
162
|
-
|
312
|
+
### No Authentication (Default)
|
313
|
+
|
314
|
+
```typescript
|
315
|
+
// postgresdk.config.ts
|
316
|
+
export default {
|
317
|
+
connectionString: "...",
|
318
|
+
// No auth config needed - routes are unprotected
|
319
|
+
};
|
320
|
+
```
|
321
|
+
|
322
|
+
### API Key Authentication
|
323
|
+
|
324
|
+
```typescript
|
325
|
+
// postgresdk.config.ts
|
326
|
+
export default {
|
327
|
+
connectionString: "...",
|
328
|
+
auth: {
|
329
|
+
strategy: "api-key",
|
330
|
+
apiKeyHeader: "x-api-key", // Optional, defaults to "x-api-key"
|
331
|
+
apiKeys: [
|
332
|
+
"your-api-key-1",
|
333
|
+
"your-api-key-2",
|
334
|
+
// Can also use environment variables
|
335
|
+
"env:API_KEYS" // Reads comma-separated keys from process.env.API_KEYS
|
336
|
+
]
|
337
|
+
}
|
338
|
+
};
|
339
|
+
|
340
|
+
// Client SDK usage
|
341
|
+
const sdk = new SDK({
|
342
|
+
baseUrl: "http://localhost:3000",
|
343
|
+
auth: { apiKey: "your-api-key-1" }
|
344
|
+
});
|
345
|
+
```
|
346
|
+
|
347
|
+
### JWT Authentication (HS256)
|
348
|
+
|
349
|
+
```typescript
|
350
|
+
// postgresdk.config.ts
|
351
|
+
export default {
|
352
|
+
connectionString: "...",
|
353
|
+
auth: {
|
354
|
+
strategy: "jwt-hs256",
|
355
|
+
jwt: {
|
356
|
+
sharedSecret: process.env.JWT_SECRET || "your-secret-key",
|
357
|
+
issuer: "my-app", // Optional: validates 'iss' claim
|
358
|
+
audience: "my-users" // Optional: validates 'aud' claim
|
359
|
+
}
|
360
|
+
}
|
361
|
+
};
|
362
|
+
|
363
|
+
// Client SDK usage with static token
|
364
|
+
const sdk = new SDK({
|
365
|
+
baseUrl: "http://localhost:3000",
|
366
|
+
auth: { jwt: "eyJhbGciOiJIUzI1NiIs..." }
|
367
|
+
});
|
368
|
+
|
369
|
+
// Or with dynamic token provider
|
370
|
+
const sdk = new SDK({
|
371
|
+
baseUrl: "http://localhost:3000",
|
372
|
+
auth: {
|
373
|
+
jwt: async () => {
|
374
|
+
// Refresh token if needed
|
375
|
+
return await getAccessToken();
|
376
|
+
}
|
377
|
+
}
|
378
|
+
});
|
379
|
+
|
380
|
+
// Or with custom auth headers
|
381
|
+
const sdk = new SDK({
|
382
|
+
baseUrl: "http://localhost:3000",
|
383
|
+
auth: async () => ({
|
384
|
+
"Authorization": `Bearer ${await getToken()}`,
|
385
|
+
"X-Tenant-ID": "tenant-123"
|
386
|
+
})
|
387
|
+
});
|
388
|
+
```
|
389
|
+
|
390
|
+
### Environment Variables in Auth Config
|
391
|
+
|
392
|
+
The auth configuration supports environment variables with the `env:` prefix:
|
393
|
+
|
394
|
+
```typescript
|
395
|
+
export default {
|
396
|
+
auth: {
|
397
|
+
strategy: "api-key",
|
398
|
+
apiKeys: ["env:API_KEYS"], // Reads from process.env.API_KEYS
|
399
|
+
|
400
|
+
// Or for JWT
|
401
|
+
strategy: "jwt-hs256",
|
402
|
+
jwt: {
|
403
|
+
sharedSecret: "env:JWT_SECRET" // Reads from process.env.JWT_SECRET
|
404
|
+
}
|
405
|
+
}
|
406
|
+
};
|
407
|
+
```
|
408
|
+
|
409
|
+
### How Auth Works
|
410
|
+
|
411
|
+
When authentication is configured:
|
412
|
+
|
413
|
+
1. **Server Side**: All generated routes are automatically protected with the configured auth middleware
|
414
|
+
2. **Client Side**: The SDK handles auth headers transparently
|
415
|
+
3. **Type Safety**: Auth configuration is fully typed
|
416
|
+
4. **Zero Overhead**: When `strategy: "none"`, no auth code is included
|
417
|
+
|
418
|
+
### JWT Token Generation Example
|
419
|
+
|
420
|
+
```typescript
|
421
|
+
// Install jose for JWT generation: npm install jose
|
422
|
+
import { SignJWT } from 'jose';
|
423
|
+
|
424
|
+
const secret = new TextEncoder().encode('your-secret-key');
|
425
|
+
|
426
|
+
const token = await new SignJWT({
|
427
|
+
sub: 'user123',
|
428
|
+
email: 'user@example.com',
|
429
|
+
roles: ['admin']
|
430
|
+
})
|
431
|
+
.setProtectedHeader({ alg: 'HS256' })
|
432
|
+
.setIssuer('my-app')
|
433
|
+
.setAudience('my-users')
|
434
|
+
.setExpirationTime('2h')
|
435
|
+
.sign(secret);
|
436
|
+
|
437
|
+
// Use with SDK
|
438
|
+
const sdk = new SDK({
|
439
|
+
baseUrl: "http://localhost:3000",
|
440
|
+
auth: { jwt: token }
|
441
|
+
});
|
442
|
+
```
|
443
|
+
|
444
|
+
## Server Integration with Hono
|
445
|
+
|
446
|
+
The generated code integrates seamlessly with [Hono](https://hono.dev/), a lightweight web framework for the Edge.
|
447
|
+
|
448
|
+
### Basic Setup
|
449
|
+
|
450
|
+
postgresdk generates a `createRouter` function that returns a Hono router with all your routes:
|
163
451
|
|
164
452
|
```typescript
|
165
453
|
import { Hono } from "hono";
|
166
454
|
import { serve } from "@hono/node-server";
|
167
455
|
import { Client } from "pg";
|
456
|
+
import { createRouter } from "./generated/server";
|
457
|
+
|
458
|
+
const app = new Hono();
|
459
|
+
|
460
|
+
// Database connection
|
461
|
+
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
462
|
+
await pg.connect();
|
168
463
|
|
169
|
-
//
|
464
|
+
// Mount all generated routes at once
|
465
|
+
const apiRouter = createRouter({ pg });
|
466
|
+
app.route("/", apiRouter);
|
467
|
+
|
468
|
+
// Start server
|
469
|
+
serve({ fetch: app.fetch, port: 3000 });
|
470
|
+
console.log("Server running on http://localhost:3000");
|
471
|
+
```
|
472
|
+
|
473
|
+
### Mounting Routes at Different Paths
|
474
|
+
|
475
|
+
The `createRouter` function returns a Hono router that can be mounted anywhere:
|
476
|
+
|
477
|
+
```typescript
|
478
|
+
import { Hono } from "hono";
|
479
|
+
import { createRouter } from "./generated/server";
|
480
|
+
|
481
|
+
const app = new Hono();
|
482
|
+
|
483
|
+
// Your existing routes
|
484
|
+
app.get("/", (c) => c.json({ message: "Welcome" }));
|
485
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
486
|
+
|
487
|
+
// Mount generated routes under /api
|
488
|
+
const apiRouter = createRouter({ pg });
|
489
|
+
app.route("/api", apiRouter); // Routes will be at /api/v1/users, /api/v1/posts, etc.
|
490
|
+
|
491
|
+
// Or mount under different version
|
492
|
+
app.route("/v2", apiRouter); // Routes will be at /v2/v1/users, /v2/v1/posts, etc.
|
493
|
+
```
|
494
|
+
|
495
|
+
### Alternative: Register Routes Directly
|
496
|
+
|
497
|
+
If you prefer to register routes directly on your app without a sub-router:
|
498
|
+
|
499
|
+
```typescript
|
500
|
+
import { registerAllRoutes } from "./generated/server";
|
501
|
+
|
502
|
+
const app = new Hono();
|
503
|
+
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
504
|
+
await pg.connect();
|
505
|
+
|
506
|
+
// Register all routes directly on app
|
507
|
+
registerAllRoutes(app, { pg });
|
508
|
+
```
|
509
|
+
|
510
|
+
### Selective Route Registration
|
511
|
+
|
512
|
+
You can also import and register individual routes:
|
513
|
+
|
514
|
+
```typescript
|
515
|
+
import { registerUsersRoutes, registerPostsRoutes } from "./generated/server";
|
516
|
+
|
517
|
+
const app = new Hono();
|
518
|
+
|
519
|
+
// Only register specific routes
|
520
|
+
registerUsersRoutes(app, { pg });
|
521
|
+
registerPostsRoutes(app, { pg });
|
522
|
+
|
523
|
+
### Adding to an Existing Hono App
|
524
|
+
|
525
|
+
```typescript
|
526
|
+
import { Hono } from "hono";
|
527
|
+
import { cors } from "hono/cors";
|
528
|
+
import { logger } from "hono/logger";
|
529
|
+
|
530
|
+
// Your existing Hono app
|
531
|
+
const app = new Hono();
|
532
|
+
|
533
|
+
// Your existing middleware
|
534
|
+
app.use("*", cors());
|
535
|
+
app.use("*", logger());
|
536
|
+
|
537
|
+
// Your existing routes
|
538
|
+
app.get("/", (c) => c.json({ message: "Hello World" }));
|
539
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
540
|
+
|
541
|
+
// Add postgresdk generated routes
|
542
|
+
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
543
|
+
await pg.connect();
|
544
|
+
|
545
|
+
// All generated routes are prefixed with /v1 by default
|
170
546
|
import { registerUsersRoutes } from "./generated/server/routes/users";
|
171
547
|
import { registerPostsRoutes } from "./generated/server/routes/posts";
|
172
548
|
|
549
|
+
registerUsersRoutes(app, { pg }); // Adds /v1/users/*
|
550
|
+
registerPostsRoutes(app, { pg }); // Adds /v1/posts/*
|
551
|
+
|
552
|
+
// Your routes continue to work alongside generated ones
|
553
|
+
app.get("/custom", (c) => c.json({ custom: true }));
|
554
|
+
```
|
555
|
+
|
556
|
+
### With Error Handling & Logging
|
557
|
+
|
558
|
+
```typescript
|
559
|
+
import { Hono } from "hono";
|
560
|
+
import { HTTPException } from "hono/http-exception";
|
561
|
+
|
173
562
|
const app = new Hono();
|
563
|
+
|
564
|
+
// Global error handling
|
565
|
+
app.onError((err, c) => {
|
566
|
+
if (err instanceof HTTPException) {
|
567
|
+
return err.getResponse();
|
568
|
+
}
|
569
|
+
console.error("Unhandled error:", err);
|
570
|
+
return c.json({ error: "Internal Server Error" }, 500);
|
571
|
+
});
|
572
|
+
|
573
|
+
// Request logging middleware
|
574
|
+
app.use("*", async (c, next) => {
|
575
|
+
const start = Date.now();
|
576
|
+
await next();
|
577
|
+
const ms = Date.now() - start;
|
578
|
+
console.log(`${c.req.method} ${c.req.path} - ${c.res.status} ${ms}ms`);
|
579
|
+
});
|
580
|
+
|
581
|
+
// Register generated routes with database
|
174
582
|
const pg = new Client({ connectionString: process.env.DATABASE_URL });
|
175
583
|
await pg.connect();
|
176
584
|
|
177
|
-
|
585
|
+
import { registerUsersRoutes } from "./generated/server/routes/users";
|
178
586
|
registerUsersRoutes(app, { pg });
|
179
|
-
|
587
|
+
```
|
180
588
|
|
181
|
-
|
589
|
+
### With Database Connection Pooling
|
590
|
+
|
591
|
+
For production, use connection pooling:
|
592
|
+
|
593
|
+
```typescript
|
594
|
+
import { Pool } from "pg";
|
595
|
+
import { Hono } from "hono";
|
596
|
+
|
597
|
+
// Use a connection pool instead of a single client
|
598
|
+
const pool = new Pool({
|
599
|
+
connectionString: process.env.DATABASE_URL,
|
600
|
+
max: 20, // Maximum number of clients in the pool
|
601
|
+
idleTimeoutMillis: 30000,
|
602
|
+
connectionTimeoutMillis: 2000,
|
603
|
+
});
|
604
|
+
|
605
|
+
const app = new Hono();
|
606
|
+
|
607
|
+
// The generated routes work with both Client and Pool
|
608
|
+
import { registerUsersRoutes } from "./generated/server/routes/users";
|
609
|
+
import { registerPostsRoutes } from "./generated/server/routes/posts";
|
610
|
+
|
611
|
+
registerUsersRoutes(app, { pg: pool });
|
612
|
+
registerPostsRoutes(app, { pg: pool });
|
613
|
+
|
614
|
+
// Graceful shutdown
|
615
|
+
process.on("SIGTERM", async () => {
|
616
|
+
await pool.end();
|
617
|
+
process.exit(0);
|
618
|
+
});
|
619
|
+
```
|
620
|
+
|
621
|
+
### With Different Deployment Targets
|
622
|
+
|
623
|
+
```typescript
|
624
|
+
// For Node.js
|
625
|
+
import { serve } from "@hono/node-server";
|
182
626
|
serve({ fetch: app.fetch, port: 3000 });
|
627
|
+
|
628
|
+
// For Cloudflare Workers
|
629
|
+
export default app;
|
630
|
+
|
631
|
+
// For Vercel
|
632
|
+
import { handle } from "@hono/vercel";
|
633
|
+
export default handle(app);
|
634
|
+
|
635
|
+
// For AWS Lambda
|
636
|
+
import { handle } from "@hono/aws-lambda";
|
637
|
+
export const handler = handle(app);
|
638
|
+
|
639
|
+
// For Deno
|
640
|
+
Deno.serve(app.fetch);
|
641
|
+
|
642
|
+
// For Bun
|
643
|
+
export default {
|
644
|
+
port: 3000,
|
645
|
+
fetch: app.fetch,
|
646
|
+
};
|
647
|
+
```
|
648
|
+
|
649
|
+
### Complete Production Example
|
650
|
+
|
651
|
+
```typescript
|
652
|
+
import { Hono } from "hono";
|
653
|
+
import { cors } from "hono/cors";
|
654
|
+
import { compress } from "hono/compress";
|
655
|
+
import { secureHeaders } from "hono/secure-headers";
|
656
|
+
import { serve } from "@hono/node-server";
|
657
|
+
import { Pool } from "pg";
|
658
|
+
|
659
|
+
// Import all generated route registrations
|
660
|
+
import { registerUsersRoutes } from "./generated/server/routes/users";
|
661
|
+
import { registerPostsRoutes } from "./generated/server/routes/posts";
|
662
|
+
import { registerCommentsRoutes } from "./generated/server/routes/comments";
|
663
|
+
|
664
|
+
// Create app with type safety
|
665
|
+
const app = new Hono();
|
666
|
+
|
667
|
+
// Production middleware stack
|
668
|
+
app.use("*", cors({
|
669
|
+
origin: process.env.ALLOWED_ORIGINS?.split(",") || "*",
|
670
|
+
credentials: true,
|
671
|
+
}));
|
672
|
+
app.use("*", compress());
|
673
|
+
app.use("*", secureHeaders());
|
674
|
+
|
675
|
+
// Health check
|
676
|
+
app.get("/health", (c) => c.json({
|
677
|
+
status: "ok",
|
678
|
+
timestamp: new Date().toISOString()
|
679
|
+
}));
|
680
|
+
|
681
|
+
// Database connection pool
|
682
|
+
const pool = new Pool({
|
683
|
+
connectionString: process.env.DATABASE_URL,
|
684
|
+
ssl: process.env.NODE_ENV === "production" ? { rejectUnauthorized: false } : false,
|
685
|
+
max: 20,
|
686
|
+
});
|
687
|
+
|
688
|
+
// Register all generated routes
|
689
|
+
registerUsersRoutes(app, { pg: pool });
|
690
|
+
registerPostsRoutes(app, { pg: pool });
|
691
|
+
registerCommentsRoutes(app, { pg: pool });
|
692
|
+
|
693
|
+
// 404 handler
|
694
|
+
app.notFound((c) => c.json({ error: "Not Found" }, 404));
|
695
|
+
|
696
|
+
// Global error handler
|
697
|
+
app.onError((err, c) => {
|
698
|
+
console.error(`Error ${c.req.method} ${c.req.path}:`, err);
|
699
|
+
return c.json({
|
700
|
+
error: process.env.NODE_ENV === "production"
|
701
|
+
? "Internal Server Error"
|
702
|
+
: err.message
|
703
|
+
}, 500);
|
704
|
+
});
|
705
|
+
|
706
|
+
// Start server
|
707
|
+
const port = parseInt(process.env.PORT || "3000");
|
708
|
+
serve({
|
709
|
+
fetch: app.fetch,
|
710
|
+
port,
|
711
|
+
hostname: "0.0.0.0"
|
712
|
+
});
|
713
|
+
|
714
|
+
console.log(`Server running on http://localhost:${port}`);
|
715
|
+
console.log(`Environment: ${process.env.NODE_ENV || "development"}`);
|
716
|
+
|
717
|
+
// Graceful shutdown
|
718
|
+
process.on("SIGTERM", async () => {
|
719
|
+
console.log("SIGTERM received, closing connections...");
|
720
|
+
await pool.end();
|
721
|
+
process.exit(0);
|
722
|
+
});
|
183
723
|
```
|
184
724
|
|
185
725
|
## CLI Options
|
@@ -210,6 +750,7 @@ Options:
|
|
210
750
|
- Node.js 20+
|
211
751
|
- PostgreSQL 12+
|
212
752
|
- TypeScript project (for using generated code)
|
753
|
+
- Optional: `jose` package for JWT authentication (auto-installed when using JWT auth)
|
213
754
|
|
214
755
|
## Development
|
215
756
|
|