@venizia/ignis-docs 0.0.1-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/mcp-server/dist/common/config.d.ts +27 -0
- package/mcp-server/dist/common/config.d.ts.map +1 -0
- package/mcp-server/dist/common/config.js +27 -0
- package/mcp-server/dist/common/config.js.map +1 -0
- package/mcp-server/dist/common/index.d.ts +3 -0
- package/mcp-server/dist/common/index.d.ts.map +1 -0
- package/mcp-server/dist/common/index.js +19 -0
- package/mcp-server/dist/common/index.js.map +1 -0
- package/mcp-server/dist/common/paths.d.ts +13 -0
- package/mcp-server/dist/common/paths.d.ts.map +1 -0
- package/mcp-server/dist/common/paths.js +23 -0
- package/mcp-server/dist/common/paths.js.map +1 -0
- package/mcp-server/dist/helpers/docs.helper.d.ts +81 -0
- package/mcp-server/dist/helpers/docs.helper.d.ts.map +1 -0
- package/mcp-server/dist/helpers/docs.helper.js +171 -0
- package/mcp-server/dist/helpers/docs.helper.js.map +1 -0
- package/mcp-server/dist/helpers/index.d.ts +3 -0
- package/mcp-server/dist/helpers/index.d.ts.map +1 -0
- package/mcp-server/dist/helpers/index.js +19 -0
- package/mcp-server/dist/helpers/index.js.map +1 -0
- package/mcp-server/dist/helpers/logger.helper.d.ts +7 -0
- package/mcp-server/dist/helpers/logger.helper.d.ts.map +1 -0
- package/mcp-server/dist/helpers/logger.helper.js +22 -0
- package/mcp-server/dist/helpers/logger.helper.js.map +1 -0
- package/mcp-server/dist/index.d.ts +3 -0
- package/mcp-server/dist/index.d.ts.map +1 -0
- package/mcp-server/dist/index.js +62 -0
- package/mcp-server/dist/index.js.map +1 -0
- package/mcp-server/dist/tools/base.tool.d.ts +98 -0
- package/mcp-server/dist/tools/base.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/base.tool.js +47 -0
- package/mcp-server/dist/tools/base.tool.js.map +1 -0
- package/mcp-server/dist/tools/get-doc-content.tool.d.ts +30 -0
- package/mcp-server/dist/tools/get-doc-content.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/get-doc-content.tool.js +127 -0
- package/mcp-server/dist/tools/get-doc-content.tool.js.map +1 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts +40 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.js +121 -0
- package/mcp-server/dist/tools/get-doc-metadata.tool.js.map +1 -0
- package/mcp-server/dist/tools/index.d.ts +8 -0
- package/mcp-server/dist/tools/index.d.ts.map +1 -0
- package/mcp-server/dist/tools/index.js +18 -0
- package/mcp-server/dist/tools/index.js.map +1 -0
- package/mcp-server/dist/tools/list-categories.tool.d.ts +20 -0
- package/mcp-server/dist/tools/list-categories.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/list-categories.tool.js +105 -0
- package/mcp-server/dist/tools/list-categories.tool.js.map +1 -0
- package/mcp-server/dist/tools/list-docs.tool.d.ts +32 -0
- package/mcp-server/dist/tools/list-docs.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/list-docs.tool.js +121 -0
- package/mcp-server/dist/tools/list-docs.tool.js.map +1 -0
- package/mcp-server/dist/tools/search-docs.tool.d.ts +32 -0
- package/mcp-server/dist/tools/search-docs.tool.d.ts.map +1 -0
- package/mcp-server/dist/tools/search-docs.tool.js +120 -0
- package/mcp-server/dist/tools/search-docs.tool.js.map +1 -0
- package/package.json +102 -0
- package/wiki/get-started/5-minute-quickstart.md +266 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +222 -0
- package/wiki/get-started/best-practices/architectural-patterns.md +129 -0
- package/wiki/get-started/best-practices/code-style-standards.md +122 -0
- package/wiki/get-started/best-practices/common-pitfalls.md +136 -0
- package/wiki/get-started/best-practices/contribution-workflow.md +145 -0
- package/wiki/get-started/best-practices/deployment-strategies.md +121 -0
- package/wiki/get-started/best-practices/performance-optimization.md +88 -0
- package/wiki/get-started/best-practices/security-guidelines.md +97 -0
- package/wiki/get-started/best-practices/troubleshooting-tips.md +100 -0
- package/wiki/get-started/building-a-crud-api.md +717 -0
- package/wiki/get-started/core-concepts/application.md +168 -0
- package/wiki/get-started/core-concepts/components.md +96 -0
- package/wiki/get-started/core-concepts/controllers.md +441 -0
- package/wiki/get-started/core-concepts/dependency-injection.md +160 -0
- package/wiki/get-started/core-concepts/persistent.md +591 -0
- package/wiki/get-started/core-concepts/services.md +88 -0
- package/wiki/get-started/index.md +65 -0
- package/wiki/get-started/mcp-docs-server.md +840 -0
- package/wiki/get-started/philosophy.md +123 -0
- package/wiki/get-started/prerequisites.md +113 -0
- package/wiki/get-started/quickstart.md +382 -0
- package/wiki/index.md +48 -0
- package/wiki/references/base/application.md +67 -0
- package/wiki/references/base/components.md +80 -0
- package/wiki/references/base/controllers.md +361 -0
- package/wiki/references/base/datasources.md +105 -0
- package/wiki/references/base/dependency-injection.md +83 -0
- package/wiki/references/base/models.md +104 -0
- package/wiki/references/base/repositories.md +118 -0
- package/wiki/references/base/services.md +97 -0
- package/wiki/references/components/authentication.md +224 -0
- package/wiki/references/components/health-check.md +190 -0
- package/wiki/references/components/index.md +61 -0
- package/wiki/references/components/request-tracker.md +35 -0
- package/wiki/references/components/socket-io.md +127 -0
- package/wiki/references/components/swagger.md +175 -0
- package/wiki/references/helpers/cron.md +94 -0
- package/wiki/references/helpers/crypto.md +117 -0
- package/wiki/references/helpers/env.md +67 -0
- package/wiki/references/helpers/error.md +80 -0
- package/wiki/references/helpers/index.md +21 -0
- package/wiki/references/helpers/inversion.md +141 -0
- package/wiki/references/helpers/logger.md +98 -0
- package/wiki/references/helpers/network.md +143 -0
- package/wiki/references/helpers/queue.md +131 -0
- package/wiki/references/helpers/redis.md +121 -0
- package/wiki/references/helpers/socket-io.md +103 -0
- package/wiki/references/helpers/storage.md +130 -0
- package/wiki/references/helpers/testing.md +115 -0
- package/wiki/references/helpers/worker-thread.md +162 -0
- package/wiki/references/src-details/core.md +249 -0
- package/wiki/references/src-details/dev-configs.md +302 -0
- package/wiki/references/src-details/docs.md +61 -0
- package/wiki/references/src-details/helpers.md +211 -0
- package/wiki/references/src-details/inversion.md +345 -0
- package/wiki/references/src-details/mcp-server.md +726 -0
- package/wiki/references/utilities/crypto.md +39 -0
- package/wiki/references/utilities/date.md +72 -0
- package/wiki/references/utilities/index.md +12 -0
- package/wiki/references/utilities/module.md +40 -0
- package/wiki/references/utilities/parse.md +68 -0
- package/wiki/references/utilities/performance.md +64 -0
- package/wiki/references/utilities/promise.md +83 -0
- package/wiki/references/utilities/request.md +66 -0
- package/wiki/references/utilities/schema.md +88 -0
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
# Building a CRUD API: A Step-by-Step Tutorial
|
|
2
|
+
|
|
3
|
+
Build a complete, database-backed REST API for managing todos. This guide covers Models, DataSources, Repositories, and Controllers - the core building blocks of Ignis applications.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- ✅ Completed [Quickstart Guide](./quickstart.md)
|
|
8
|
+
- ✅ PostgreSQL installed and running
|
|
9
|
+
- ✅ Database created (see [Prerequisites](./prerequisites.md))
|
|
10
|
+
|
|
11
|
+
## What You'll Build
|
|
12
|
+
|
|
13
|
+
**Components:**
|
|
14
|
+
- `Todo` Model - Data structure definition
|
|
15
|
+
- `PostgresDataSource` - Database connection
|
|
16
|
+
- `TodoRepository` - Data access layer
|
|
17
|
+
- `TodoController` - REST API endpoints
|
|
18
|
+
|
|
19
|
+
**Endpoints:**
|
|
20
|
+
- `POST /todos` - Create todo
|
|
21
|
+
- `GET /todos` - List all todos
|
|
22
|
+
- `GET /todos/:id` - Get single todo
|
|
23
|
+
- `PATCH /todos/:id` - Update todo
|
|
24
|
+
- `DELETE /todos/:id` - Delete todo
|
|
25
|
+
|
|
26
|
+
### Architecture Flow
|
|
27
|
+
|
|
28
|
+
Here's how a request flows through your application:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
32
|
+
│ HTTP Request │
|
|
33
|
+
│ GET /api/todos/:id │
|
|
34
|
+
└────────────────────┬────────────────────────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
▼
|
|
37
|
+
┌──────────────────┐
|
|
38
|
+
│ TodoController │ ← Handles HTTP, validates input
|
|
39
|
+
│ │
|
|
40
|
+
│ @get('/api/...')│
|
|
41
|
+
└─────────┬────────┘
|
|
42
|
+
│
|
|
43
|
+
│ calls repository.findById()
|
|
44
|
+
▼
|
|
45
|
+
┌──────────────────┐
|
|
46
|
+
│ TodoRepository │ ← Type-safe data access
|
|
47
|
+
│ │
|
|
48
|
+
│ findById(id) │
|
|
49
|
+
└─────────┬────────┘
|
|
50
|
+
│
|
|
51
|
+
│ uses dataSource.connector
|
|
52
|
+
▼
|
|
53
|
+
┌──────────────────┐
|
|
54
|
+
│PostgresDataSource│ ← Database connection
|
|
55
|
+
│ │
|
|
56
|
+
│ Drizzle ORM │
|
|
57
|
+
└─────────┬────────┘
|
|
58
|
+
│
|
|
59
|
+
│ executes SQL query
|
|
60
|
+
▼
|
|
61
|
+
┌──────────────────┐
|
|
62
|
+
│ PostgreSQL │ ← Actual database
|
|
63
|
+
│ │
|
|
64
|
+
│ Todo table │
|
|
65
|
+
└─────────┬────────┘
|
|
66
|
+
│
|
|
67
|
+
│ returns data
|
|
68
|
+
▼
|
|
69
|
+
┌──────────────────┐
|
|
70
|
+
│ JSON Response │
|
|
71
|
+
│ │
|
|
72
|
+
│ { id, title,..} │
|
|
73
|
+
└──────────────────┘
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Key Points:**
|
|
77
|
+
1. **Controller** - Entry point for HTTP requests
|
|
78
|
+
2. **Repository** - Abstracts database operations (you could swap PostgreSQL for MySQL without changing controller)
|
|
79
|
+
3. **DataSource** - Manages connection to database
|
|
80
|
+
4. **Model** - Defines what the data looks like
|
|
81
|
+
|
|
82
|
+
This separation makes code:
|
|
83
|
+
- **Testable** - Mock repository in tests
|
|
84
|
+
- **Maintainable** - Clear responsibility for each layer
|
|
85
|
+
- **Flexible** - Change database without touching business logic
|
|
86
|
+
|
|
87
|
+
## Step 0: Install Database Dependencies
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Add database packages
|
|
91
|
+
bun add drizzle-orm drizzle-zod pg lodash
|
|
92
|
+
|
|
93
|
+
# Add dev dependencies for migrations
|
|
94
|
+
bun add -d drizzle-kit @types/pg @types/lodash
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Step 1: Define the Model
|
|
98
|
+
|
|
99
|
+
Models combine Drizzle ORM schemas with Entity classes to define your data structure.
|
|
100
|
+
|
|
101
|
+
Create `src/models/todo.model.ts`:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// src/models/todo.model.ts
|
|
105
|
+
import {
|
|
106
|
+
BaseEntity,
|
|
107
|
+
createRelations,
|
|
108
|
+
generateIdColumnDefs,
|
|
109
|
+
generateTzColumnDefs,
|
|
110
|
+
model,
|
|
111
|
+
TTableObject,
|
|
112
|
+
} from '@venizia/ignis';
|
|
113
|
+
import { boolean, pgTable, text } from 'drizzle-orm/pg-core';
|
|
114
|
+
|
|
115
|
+
// 1. Define the Drizzle schema for the 'Todo' table
|
|
116
|
+
// Note: Use string literal 'Todo' to avoid circular reference
|
|
117
|
+
export const todoTable = pgTable('Todo', {
|
|
118
|
+
...generateIdColumnDefs({ id: { dataType: 'string' } }),
|
|
119
|
+
...generateTzColumnDefs(),
|
|
120
|
+
title: text('title').notNull(),
|
|
121
|
+
description: text('description'),
|
|
122
|
+
isCompleted: boolean('is_completed').default(false),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// 2. Define relations (empty for now, but required)
|
|
126
|
+
export const todoRelations = createRelations({
|
|
127
|
+
source: todoTable,
|
|
128
|
+
relations: [],
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// 3. Define the TypeScript type for a Todo object
|
|
132
|
+
export type TTodoSchema = typeof todoTable;
|
|
133
|
+
export type TTodo = TTableObject<TTodoSchema>;
|
|
134
|
+
|
|
135
|
+
// 4. Create the Entity class, decorated with @model
|
|
136
|
+
@model({ type: 'entity' })
|
|
137
|
+
export class Todo extends BaseEntity<TTodoSchema> {
|
|
138
|
+
static readonly TABLE_NAME = 'Todo';
|
|
139
|
+
|
|
140
|
+
constructor() {
|
|
141
|
+
super({ name: Todo.TABLE_NAME, schema: todoTable });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Schema Enrichers:**
|
|
147
|
+
- `generateIdColumnDefs()` - Adds `id` column (UUID primary key)
|
|
148
|
+
- `generateTzColumnDefs()` - Adds `createdAt` and `modifiedAt` timestamps
|
|
149
|
+
|
|
150
|
+
> **Deep Dive:** See [Models & Enrichers Reference](../references/base/models.md#schema-enrichers) for all available enrichers and options.
|
|
151
|
+
|
|
152
|
+
## Step 2: Configure Database Connection
|
|
153
|
+
|
|
154
|
+
### Understanding Environment Variables
|
|
155
|
+
|
|
156
|
+
Before we connect to the database, let's understand **environment variables**.
|
|
157
|
+
|
|
158
|
+
**What are they?**
|
|
159
|
+
Environment variables are configuration values stored outside your code. Think of them as settings that can change without modifying your source code.
|
|
160
|
+
|
|
161
|
+
**Why use them?**
|
|
162
|
+
```typescript
|
|
163
|
+
// ❌ BAD: Hardcoded values
|
|
164
|
+
const password = "secret123"; // Now it's in Git history forever!
|
|
165
|
+
|
|
166
|
+
// ✅ GOOD: Environment variable
|
|
167
|
+
const password = process.env.DB_PASSWORD; // Value comes from .env file
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Benefits:**
|
|
171
|
+
1. **Security** - Never commit passwords to Git
|
|
172
|
+
2. **Flexibility** - Different values for dev/staging/production
|
|
173
|
+
3. **Team Collaboration** - Each developer has their own `.env` file
|
|
174
|
+
|
|
175
|
+
**The `APP_ENV_` prefix:**
|
|
176
|
+
Ignis uses `APP_ENV_` prefix for all its environment variables. This prevents conflicts with system variables (like `PATH`, `HOME`, etc.).
|
|
177
|
+
|
|
178
|
+
### Create `.env` File
|
|
179
|
+
|
|
180
|
+
Create `.env` in your project root with your database credentials:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# .env
|
|
184
|
+
APP_ENV_POSTGRES_HOST=localhost
|
|
185
|
+
APP_ENV_POSTGRES_PORT=5432
|
|
186
|
+
APP_ENV_POSTGRES_USERNAME=postgres
|
|
187
|
+
APP_ENV_POSTGRES_PASSWORD=your_password_here
|
|
188
|
+
APP_ENV_POSTGRES_DATABASE=todo_db
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Replace these values:**
|
|
192
|
+
- `your_password_here` - Your PostgreSQL password (or leave blank if no password)
|
|
193
|
+
- `todo_db` - The database you created in [Prerequisites](./prerequisites.md#database-setup)
|
|
194
|
+
|
|
195
|
+
**Important:** Add `.env` to your `.gitignore`:
|
|
196
|
+
```bash
|
|
197
|
+
echo ".env" >> .gitignore
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
This prevents accidentally committing secrets to Git.
|
|
201
|
+
|
|
202
|
+
Create `src/datasources/postgres.datasource.ts`:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// src/datasources/postgres.datasource.ts
|
|
206
|
+
import { Todo, todoRelations, todoTable } from '@/models/todo.model';
|
|
207
|
+
import {
|
|
208
|
+
BaseDataSource,
|
|
209
|
+
datasource,
|
|
210
|
+
TNodePostgresConnector,
|
|
211
|
+
ValueOrPromise,
|
|
212
|
+
} from '@venizia/ignis';
|
|
213
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
214
|
+
import { Pool } from 'pg';
|
|
215
|
+
|
|
216
|
+
interface IDSConfigs {
|
|
217
|
+
connection: {
|
|
218
|
+
host?: string;
|
|
219
|
+
port?: number;
|
|
220
|
+
user?: string;
|
|
221
|
+
password?: string;
|
|
222
|
+
database?: string;
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@datasource()
|
|
227
|
+
export class PostgresDataSource extends BaseDataSource<TNodePostgresConnector, IDSConfigs> {
|
|
228
|
+
constructor() {
|
|
229
|
+
super({
|
|
230
|
+
name: PostgresDataSource.name,
|
|
231
|
+
driver: 'node-postgres',
|
|
232
|
+
config: {
|
|
233
|
+
connection: {
|
|
234
|
+
host: process.env.APP_ENV_POSTGRES_HOST,
|
|
235
|
+
port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
|
|
236
|
+
user: process.env.APP_ENV_POSTGRES_USERNAME,
|
|
237
|
+
password: process.env.APP_ENV_POSTGRES_PASSWORD,
|
|
238
|
+
database: process.env.APP_ENV_POSTGRES_DATABASE,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
// Register all your models and their relations here
|
|
242
|
+
schema: Object.assign(
|
|
243
|
+
{},
|
|
244
|
+
{ [Todo.TABLE_NAME]: todoTable },
|
|
245
|
+
todoRelations.relations,
|
|
246
|
+
),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
override configure(): ValueOrPromise<void> {
|
|
251
|
+
this.connector = drizzle({
|
|
252
|
+
client: new Pool(this.settings.connection),
|
|
253
|
+
schema: this.schema,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
override async connect(): Promise<TNodePostgresConnector | undefined> {
|
|
258
|
+
await (this.connector.client as Pool).connect();
|
|
259
|
+
return this.connector;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
override async disconnect(): Promise<void> {
|
|
263
|
+
await (this.connector.client as Pool).end();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Key Points:**
|
|
269
|
+
- Registers `todoTable` and `todoRelations` in the schema
|
|
270
|
+
- Uses environment variables for connection config
|
|
271
|
+
- Implements connection lifecycle methods
|
|
272
|
+
|
|
273
|
+
> **Deep Dive:** See [DataSources Reference](../references/base/datasources.md) for advanced configuration and multiple database support.
|
|
274
|
+
|
|
275
|
+
## Step 3: Create the Repository
|
|
276
|
+
|
|
277
|
+
Repositories provide type-safe CRUD operations using `DefaultCRUDRepository`.
|
|
278
|
+
|
|
279
|
+
Create `src/repositories/todo.repository.ts`:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// src/repositories/todo.repository.ts
|
|
283
|
+
import { Todo, todoRelations, TTodoSchema } from '@/models/todo.model';
|
|
284
|
+
import {
|
|
285
|
+
DefaultCRUDRepository,
|
|
286
|
+
IDataSource,
|
|
287
|
+
inject,
|
|
288
|
+
repository,
|
|
289
|
+
} from '@venizia/ignis';
|
|
290
|
+
|
|
291
|
+
@repository()
|
|
292
|
+
export class TodoRepository extends DefaultCRUDRepository<TTodoSchema> {
|
|
293
|
+
constructor(
|
|
294
|
+
@inject({ key: 'datasources.PostgresDataSource' })
|
|
295
|
+
dataSource: IDataSource,
|
|
296
|
+
) {
|
|
297
|
+
super({
|
|
298
|
+
dataSource,
|
|
299
|
+
entityClass: Todo,
|
|
300
|
+
relations: todoRelations.definitions,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Available Methods:**
|
|
307
|
+
`create()`, `find()`, `findOne()`, `findById()`, `updateById()`, `updateAll()`, `deleteById()`, `deleteAll()`, `count()`
|
|
308
|
+
|
|
309
|
+
> **Deep Dive:** See [Repositories Reference](../references/base/repositories.md) for query options and advanced filtering.
|
|
310
|
+
|
|
311
|
+
## Step 4: Create the Controller
|
|
312
|
+
|
|
313
|
+
`ControllerFactory` generates a full CRUD controller with automatic validation and OpenAPI docs.
|
|
314
|
+
|
|
315
|
+
Create `src/controllers/todo.controller.ts`:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// src/controllers/todo.controller.ts
|
|
319
|
+
import { Todo } from '@/models/todo.model';
|
|
320
|
+
import { TodoRepository } from '@/repositories/todo.repository';
|
|
321
|
+
import {
|
|
322
|
+
BindingKeys,
|
|
323
|
+
BindingNamespaces,
|
|
324
|
+
controller,
|
|
325
|
+
ControllerFactory,
|
|
326
|
+
inject,
|
|
327
|
+
} from '@venizia/ignis';
|
|
328
|
+
|
|
329
|
+
const BASE_PATH = '/todos';
|
|
330
|
+
|
|
331
|
+
// 1. The factory generates a controller class with all CRUD routes
|
|
332
|
+
const _Controller = ControllerFactory.defineCrudController({
|
|
333
|
+
repository: { name: TodoRepository.name },
|
|
334
|
+
controller: {
|
|
335
|
+
name: 'TodoController',
|
|
336
|
+
basePath: BASE_PATH,
|
|
337
|
+
},
|
|
338
|
+
entity: () => Todo, // The entity is used to generate OpenAPI schemas
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// 2. Extend the generated controller to inject the repository
|
|
342
|
+
@controller({ path: BASE_PATH })
|
|
343
|
+
export class TodoController extends _Controller {
|
|
344
|
+
constructor(
|
|
345
|
+
@inject({
|
|
346
|
+
key: BindingKeys.build({
|
|
347
|
+
namespace: BindingNamespaces.REPOSITORY,
|
|
348
|
+
key: TodoRepository.name,
|
|
349
|
+
}),
|
|
350
|
+
})
|
|
351
|
+
repository: TodoRepository,
|
|
352
|
+
) {
|
|
353
|
+
super(repository);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Auto-generated Endpoints:**
|
|
359
|
+
| Method | Path | Description |
|
|
360
|
+
|--------|------|-------------|
|
|
361
|
+
| GET | `/todos` | List all todos |
|
|
362
|
+
| GET | `/todos/:id` | Get todo by ID |
|
|
363
|
+
| GET | `/todos/find-one` | Find one todo by filter |
|
|
364
|
+
| GET | `/todos/count` | Count todos |
|
|
365
|
+
| POST | `/todos` | Create todo |
|
|
366
|
+
| PATCH | `/todos/:id` | Update todo by ID |
|
|
367
|
+
| PATCH | `/todos` | Update multiple todos |
|
|
368
|
+
| DELETE | `/todos/:id` | Delete todo by ID |
|
|
369
|
+
| DELETE | `/todos` | Delete multiple todos |
|
|
370
|
+
|
|
371
|
+
> **Deep Dive:** See [ControllerFactory Reference](../references/base/controllers.md#controllerfactory) for customization options.
|
|
372
|
+
|
|
373
|
+
## Step 5: Register Components
|
|
374
|
+
|
|
375
|
+
Update `src/application.ts` to register all components:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// src/application.ts
|
|
379
|
+
import { BaseApplication, IApplicationConfigs, IApplicationInfo, ValueOrPromise } from '@venizia/ignis';
|
|
380
|
+
import { HelloController } from './controllers/hello.controller';
|
|
381
|
+
import packageJson from '../package.json';
|
|
382
|
+
|
|
383
|
+
// Import our new components
|
|
384
|
+
import { PostgresDataSource } from './datasources/postgres.datasource';
|
|
385
|
+
import { TodoRepository } from './repositories/todo.repository';
|
|
386
|
+
import { TodoController } from './controllers/todo.controller';
|
|
387
|
+
|
|
388
|
+
export const appConfigs: IApplicationConfigs = {
|
|
389
|
+
host: process.env.HOST ?? '0.0.0.0',
|
|
390
|
+
port: +(process.env.PORT ?? 3000),
|
|
391
|
+
path: { base: '/api', isStrict: true },
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
export class Application extends BaseApplication {
|
|
395
|
+
override getAppInfo(): ValueOrPromise<IApplicationInfo> {
|
|
396
|
+
return packageJson;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
staticConfigure(): void {}
|
|
400
|
+
|
|
401
|
+
setupMiddlewares(): ValueOrPromise<void> {}
|
|
402
|
+
|
|
403
|
+
preConfigure(): ValueOrPromise<void> {
|
|
404
|
+
// 1. Register datasource
|
|
405
|
+
this.dataSource(PostgresDataSource);
|
|
406
|
+
|
|
407
|
+
// 2. Register repository
|
|
408
|
+
this.repository(TodoRepository);
|
|
409
|
+
|
|
410
|
+
// 3. Register controllers
|
|
411
|
+
this.controller(HelloController);
|
|
412
|
+
this.controller(TodoController);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
postConfigure(): ValueOrPromise<void> {}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Step 6: Run Database Migration
|
|
420
|
+
|
|
421
|
+
### Understanding Database Migrations
|
|
422
|
+
|
|
423
|
+
**The Problem:**
|
|
424
|
+
Your database is currently empty. It has no `Todo` table. But your code expects one!
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
// Your code says:
|
|
428
|
+
todoTable = pgTable('Todo', { id: ..., title: ..., ... });
|
|
429
|
+
|
|
430
|
+
// But PostgreSQL doesn't have a 'Todo' table yet!
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**What is a Migration?**
|
|
434
|
+
A migration is a script that creates or modifies database tables. Think of it as "Git commits for your database schema."
|
|
435
|
+
|
|
436
|
+
**Example Migration:**
|
|
437
|
+
```sql
|
|
438
|
+
-- This is what Drizzle will generate and run for you
|
|
439
|
+
CREATE TABLE "Todo" (
|
|
440
|
+
"id" UUID PRIMARY KEY,
|
|
441
|
+
"title" TEXT NOT NULL,
|
|
442
|
+
"description" TEXT,
|
|
443
|
+
"is_completed" BOOLEAN DEFAULT false,
|
|
444
|
+
"created_at" TIMESTAMP DEFAULT NOW(),
|
|
445
|
+
"modified_at" TIMESTAMP DEFAULT NOW()
|
|
446
|
+
);
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Why not create tables manually?**
|
|
450
|
+
- **Team Collaboration** - Everyone runs the same migrations, databases stay in sync
|
|
451
|
+
- **Version Control** - Schema changes are tracked in Git
|
|
452
|
+
- **Rollback** - Can undo changes if something breaks
|
|
453
|
+
|
|
454
|
+
### Create Migration Config
|
|
455
|
+
|
|
456
|
+
Create `src/migration.ts`:
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
// src/migration.ts
|
|
460
|
+
import { defineConfig } from 'drizzle-kit';
|
|
461
|
+
|
|
462
|
+
export default defineConfig({
|
|
463
|
+
schema: './src/models/todo.model.ts', // Where your model definitions are
|
|
464
|
+
out: './migration', // Where to save generated SQL files
|
|
465
|
+
dialect: 'postgresql', // Database type
|
|
466
|
+
dbCredentials: {
|
|
467
|
+
// Use the same .env values as your datasource
|
|
468
|
+
host: process.env.APP_ENV_POSTGRES_HOST!,
|
|
469
|
+
port: +(process.env.APP_ENV_POSTGRES_PORT ?? 5432),
|
|
470
|
+
user: process.env.APP_ENV_POSTGRES_USERNAME,
|
|
471
|
+
password: process.env.APP_ENV_POSTGRES_PASSWORD,
|
|
472
|
+
database: process.env.APP_ENV_POSTGRES_DATABASE!,
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Run the Migration
|
|
478
|
+
|
|
479
|
+
Run the migration (using the script from [Quickstart](./quickstart.md#5-run-your-application)):
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
bun run migrate:dev
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**What happens when you run this:**
|
|
486
|
+
|
|
487
|
+
1. **Reads** `src/models/todo.model.ts` to see what your schema looks like
|
|
488
|
+
2. **Generates SQL** to create the `Todo` table
|
|
489
|
+
3. **Connects** to your PostgreSQL database
|
|
490
|
+
4. **Executes** the SQL to create the table
|
|
491
|
+
5. **Saves** migration files to `./migration/` folder (for version control)
|
|
492
|
+
|
|
493
|
+
**Expected output:**
|
|
494
|
+
```
|
|
495
|
+
Reading schema...
|
|
496
|
+
Generating migration...
|
|
497
|
+
Executing migration...
|
|
498
|
+
✓ Done!
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Verify it worked:**
|
|
502
|
+
```bash
|
|
503
|
+
psql -U postgres -d todo_db -c "\d Todo"
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
You should see the `Todo` table structure with all your columns!
|
|
507
|
+
|
|
508
|
+
## Step 7: Run and Test
|
|
509
|
+
|
|
510
|
+
Start your application:
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
bun run server:dev
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Test the API endpoints:
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
# Create a todo
|
|
520
|
+
curl -X POST http://localhost:3000/api/todos \
|
|
521
|
+
-H "Content-Type: application/json" \
|
|
522
|
+
-d '{"title":"Learn Ignis","description":"Complete tutorial"}'
|
|
523
|
+
|
|
524
|
+
# Get all todos
|
|
525
|
+
curl http://localhost:3000/api/todos
|
|
526
|
+
|
|
527
|
+
# Get todo by ID (replace {id} with actual ID from create response)
|
|
528
|
+
curl http://localhost:3000/api/todos/{id}
|
|
529
|
+
|
|
530
|
+
# Update todo
|
|
531
|
+
curl -X PATCH http://localhost:3000/api/todos/{id} \
|
|
532
|
+
-H "Content-Type: application/json" \
|
|
533
|
+
-d '{"isCompleted":true}'
|
|
534
|
+
|
|
535
|
+
# Delete todo
|
|
536
|
+
curl -X DELETE http://localhost:3000/api/todos/{id}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**View API Documentation:**
|
|
540
|
+
Open `http://localhost:3000/docs` in your browser to see interactive Swagger UI.
|
|
541
|
+
|
|
542
|
+
🎉 **Congratulations!** You've built a complete CRUD API with:
|
|
543
|
+
- ✅ Type-safe database operations
|
|
544
|
+
- ✅ Automatic request validation
|
|
545
|
+
- ✅ Auto-generated OpenAPI documentation
|
|
546
|
+
- ✅ Clean, maintainable architecture
|
|
547
|
+
|
|
548
|
+
## What Could Go Wrong? Common Errors
|
|
549
|
+
|
|
550
|
+
### Error: "Binding 'datasources.PostgresDataSource' not found"
|
|
551
|
+
|
|
552
|
+
**Cause:** Forgot to register DataSource in `application.ts`
|
|
553
|
+
|
|
554
|
+
**Fix:**
|
|
555
|
+
```typescript
|
|
556
|
+
// In application.ts preConfigure():
|
|
557
|
+
this.dataSource(PostgresDataSource); // ← Make sure this is here!
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**Order matters:** DataSource must be registered before Repository.
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
### Error: "connection refused" or "ECONNREFUSED"
|
|
565
|
+
|
|
566
|
+
**Cause:** PostgreSQL isn't running, or wrong connection details in `.env`
|
|
567
|
+
|
|
568
|
+
**Fix:**
|
|
569
|
+
```bash
|
|
570
|
+
# Check if PostgreSQL is running:
|
|
571
|
+
psql -U postgres -c "SELECT 1;"
|
|
572
|
+
|
|
573
|
+
# If not running, start it:
|
|
574
|
+
brew services start postgresql@14 # macOS
|
|
575
|
+
sudo service postgresql start # Linux
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Verify `.env` values match your PostgreSQL setup.**
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
### Error: "relation 'Todo' does not exist"
|
|
583
|
+
|
|
584
|
+
**Cause:** Forgot to run database migration
|
|
585
|
+
|
|
586
|
+
**Fix:**
|
|
587
|
+
```bash
|
|
588
|
+
bun run migrate:dev
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
**Verify the table exists:**
|
|
592
|
+
```bash
|
|
593
|
+
psql -U postgres -d todo_db -c "\dt"
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
You should see `Todo` in the list.
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
### Error: 404 Not Found on `/api/todos`
|
|
601
|
+
|
|
602
|
+
**Cause:** Controller not registered or wrong path configuration
|
|
603
|
+
|
|
604
|
+
**Fix:**
|
|
605
|
+
```typescript
|
|
606
|
+
// In application.ts preConfigure():
|
|
607
|
+
this.controller(TodoController); // ← Make sure this is here!
|
|
608
|
+
|
|
609
|
+
// Check appConfigs:
|
|
610
|
+
path: { base: '/api', isStrict: true }, // All routes start with /api
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Debug:** Set `debug.showRoutes: true` in appConfigs to see all registered routes on startup.
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
### Error: "Invalid JSON" when creating todo
|
|
618
|
+
|
|
619
|
+
**Cause:** Missing `Content-Type: application/json` header
|
|
620
|
+
|
|
621
|
+
**Fix:**
|
|
622
|
+
```bash
|
|
623
|
+
# Make sure you include the header:
|
|
624
|
+
curl -X POST http://localhost:3000/api/todos \
|
|
625
|
+
-H "Content-Type: application/json" \ # ← This line!
|
|
626
|
+
-d '{"title":"Learn Ignis"}'
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Test Your Understanding: Build a Second Feature
|
|
632
|
+
|
|
633
|
+
Now that you've built the Todo API, try building a **User** feature on your own!
|
|
634
|
+
|
|
635
|
+
**Requirements:**
|
|
636
|
+
- Create `/api/users` endpoint
|
|
637
|
+
- Users should have: `id`, `email`, `name`, `createdAt`, `modifiedAt`
|
|
638
|
+
- Use `ControllerFactory` for CRUD operations
|
|
639
|
+
|
|
640
|
+
**Challenge checklist:**
|
|
641
|
+
- [ ] Create `src/models/user.model.ts`
|
|
642
|
+
- [ ] Update `PostgresDataSource` to include User schema
|
|
643
|
+
- [ ] Create `src/repositories/user.repository.ts`
|
|
644
|
+
- [ ] Create `src/controllers/user.controller.ts`
|
|
645
|
+
- [ ] Register repository and controller in `application.ts`
|
|
646
|
+
- [ ] Run migration: `bun run migrate:dev`
|
|
647
|
+
- [ ] Test with curl
|
|
648
|
+
|
|
649
|
+
**Hint:** Follow the exact same pattern as `Todo`. The only changes are the model name and fields!
|
|
650
|
+
|
|
651
|
+
**Solution:** If you get stuck, check the [API Usage Examples](./best-practices/api-usage-examples.md) guide.
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## Next Steps
|
|
656
|
+
|
|
657
|
+
### Adding Business Logic with Services
|
|
658
|
+
|
|
659
|
+
For complex validation or business rules, create a Service layer:
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
// src/services/todo.service.ts
|
|
663
|
+
import { BaseService, inject } from '@venizia/ignis';
|
|
664
|
+
import { TodoRepository } from '@/repositories/todo.repository';
|
|
665
|
+
|
|
666
|
+
export class TodoService extends BaseService {
|
|
667
|
+
constructor(
|
|
668
|
+
@inject({ key: 'repositories.TodoRepository' })
|
|
669
|
+
private todoRepository: TodoRepository,
|
|
670
|
+
) {
|
|
671
|
+
super({ scope: TodoService.name });
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async createTodo(data: any) {
|
|
675
|
+
// Business logic validation
|
|
676
|
+
if (data.title.length < 3) {
|
|
677
|
+
throw new Error('Title too short');
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Check for duplicates
|
|
681
|
+
const existing = await this.todoRepository.findOne({
|
|
682
|
+
filter: { where: { title: data.title } },
|
|
683
|
+
});
|
|
684
|
+
if (existing) {
|
|
685
|
+
throw new Error('Todo already exists');
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return this.todoRepository.create({ data });
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Register in `application.ts`:
|
|
694
|
+
```typescript
|
|
695
|
+
this.service(TodoService);
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
> **Deep Dive:** See [Services Reference](./core-concepts/services.md) for best practices and advanced patterns.
|
|
699
|
+
|
|
700
|
+
## Continue Your Journey
|
|
701
|
+
|
|
702
|
+
You now have a fully functional CRUD API! Here's what to explore next:
|
|
703
|
+
|
|
704
|
+
**Core Concepts:**
|
|
705
|
+
1. [Application Architecture](./core-concepts/application.md) - Understand the framework structure
|
|
706
|
+
2. [Dependency Injection](./core-concepts/dependency-injection.md) - Master DI patterns
|
|
707
|
+
3. [Components](./core-concepts/components.md) - Build reusable modules
|
|
708
|
+
|
|
709
|
+
**Add Features:**
|
|
710
|
+
1. [Authentication](../references/components/authentication.md) - Add JWT authentication
|
|
711
|
+
2. [Custom Routes](./best-practices/api-usage-examples.md) - Beyond CRUD operations
|
|
712
|
+
3. [Relationships](./core-concepts/persistent.md#querying-with-relations) - Link todos to users
|
|
713
|
+
|
|
714
|
+
**Production:**
|
|
715
|
+
1. [Deployment Strategies](./best-practices/deployment-strategies.md) - Deploy your API
|
|
716
|
+
2. [Performance Optimization](./best-practices/performance-optimization.md) - Make it faster
|
|
717
|
+
3. [Security Guidelines](./best-practices/security-guidelines.md) - Secure your API
|