@rabstack/rab-api 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 ADDED
@@ -0,0 +1,393 @@
1
+ # rab-api
2
+
3
+ A TypeScript REST API framework built on Express.js with decorator-based routing, dependency injection, and built-in validation.
4
+
5
+ ## Features
6
+
7
+ - 🎯 Decorator-based routing with TypeScript
8
+ - 🔒 Built-in JWT authentication
9
+ - ✅ Request validation with Joi schemas
10
+ - 💉 Dependency injection (TypeDI)
11
+ - 🔐 Role-based access control
12
+ - 📝 Full TypeScript type safety
13
+ - 🚀 Production-ready
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install rab-api
19
+ ```
20
+
21
+ **Peer dependencies:**
22
+ ```bash
23
+ npm install express joi typedi reflect-metadata jsonwebtoken compose-middleware
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ **1. Create a controller:**
29
+
30
+ ```typescript
31
+ import { Get, RabApiGet, GetController } from 'rab-api';
32
+
33
+ type ControllerT = GetController<{ status: string }>;
34
+
35
+ @Get('/health')
36
+ export class HealthCheck implements RabApiGet<ControllerT> {
37
+ handler: ControllerT['request'] = async () => {
38
+ return { status: 'ok' };
39
+ };
40
+ }
41
+ ```
42
+
43
+ **2. Bootstrap your app:**
44
+
45
+ ```typescript
46
+ import { RabApi } from 'rab-api';
47
+ import express from 'express';
48
+
49
+ const app = RabApi.createApp({
50
+ auth: {
51
+ jwt: {
52
+ secret_key: process.env.JWT_SECRET!,
53
+ algorithms: ['HS256'],
54
+ },
55
+ },
56
+ });
57
+
58
+ app.use(express.json());
59
+
60
+ app.route({
61
+ basePath: '/api',
62
+ controllers: [HealthCheck],
63
+ });
64
+
65
+ app.listen(3000);
66
+ ```
67
+
68
+ ## Core Concepts
69
+
70
+ ### Controllers
71
+
72
+ Controllers handle HTTP requests using decorators:
73
+
74
+ ```typescript
75
+ import { Post, RabApiPost, PostController } from 'rab-api';
76
+ import * as Joi from 'joi';
77
+
78
+ type CreateUserBody = { name: string; email: string };
79
+ type UserResponse = { id: string; name: string; email: string };
80
+ type ControllerT = PostController<CreateUserBody, UserResponse>;
81
+
82
+ const schema = Joi.object({
83
+ name: Joi.string().required(),
84
+ email: Joi.string().email().required(),
85
+ });
86
+
87
+ @Post('/users', { bodySchema: schema })
88
+ export class CreateUser implements RabApiPost<ControllerT> {
89
+ handler: ControllerT['request'] = async (request) => {
90
+ return { id: '1', ...request.body };
91
+ };
92
+ }
93
+ ```
94
+
95
+ **Available decorators:**
96
+ - `@Get(path, options?)` - GET requests
97
+ - `@Post(path, options?)` - POST requests
98
+ - `@Put(path, options?)` - PUT requests
99
+ - `@Patch(path, options?)` - PATCH requests
100
+ - `@Delete(path, options?)` - DELETE requests
101
+
102
+ ### Dependency Injection
103
+
104
+ Controllers support constructor injection via TypeDI:
105
+
106
+ ```typescript
107
+ import { Injectable } from 'rab-api';
108
+
109
+ @Injectable()
110
+ class UserService {
111
+ async findAll() {
112
+ return [];
113
+ }
114
+ }
115
+
116
+ @Get('/users')
117
+ export class ListUsers implements RabApiGet<ControllerT> {
118
+ constructor(private userService: UserService) {}
119
+
120
+ handler: ControllerT['request'] = async () => {
121
+ return await this.userService.findAll();
122
+ };
123
+ }
124
+ ```
125
+
126
+ ### Routing
127
+
128
+ Group related controllers with routers:
129
+
130
+ ```typescript
131
+ app.route({
132
+ basePath: '/users',
133
+ controllers: [ListUsers, CreateUser, UpdateUser, DeleteUser],
134
+ });
135
+
136
+ // Nested routes
137
+ app.route({
138
+ basePath: '/users',
139
+ controllers: [
140
+ ListUsers,
141
+ RabApi.createRouter({
142
+ basePath: '/:userId/posts',
143
+ controllers: [ListPosts, CreatePost],
144
+ }),
145
+ ],
146
+ });
147
+ ```
148
+
149
+ ## Validation
150
+
151
+ ### Request Body
152
+
153
+ ```typescript
154
+ const createProductSchema = Joi.object({
155
+ name: Joi.string().min(3).required(),
156
+ price: Joi.number().positive().required(),
157
+ });
158
+
159
+ @Post('/products', { bodySchema: createProductSchema })
160
+ export class CreateProduct implements RabApiPost<ControllerT> {
161
+ handler: ControllerT['request'] = async (request) => {
162
+ const { name, price } = request.body; // validated
163
+ return { id: '1', name, price };
164
+ };
165
+ }
166
+ ```
167
+
168
+ ### Query Parameters
169
+
170
+ ```typescript
171
+ const listSchema = Joi.object({
172
+ page: Joi.number().integer().min(1).default(1),
173
+ limit: Joi.number().integer().min(1).max(100).default(10),
174
+ });
175
+
176
+ @Get('/products', { querySchema: listSchema })
177
+ export class ListProducts implements RabApiGet<ControllerT> {
178
+ handler: ControllerT['request'] = async (request) => {
179
+ const { page, limit } = request.query; // validated
180
+ return { items: [], page, limit };
181
+ };
182
+ }
183
+ ```
184
+
185
+ ## Authentication
186
+
187
+ ### JWT Setup
188
+
189
+ ```typescript
190
+ const app = RabApi.createApp({
191
+ auth: {
192
+ jwt: {
193
+ secret_key: process.env.JWT_SECRET!,
194
+ algorithms: ['HS256'],
195
+ },
196
+ },
197
+ });
198
+ ```
199
+
200
+ ### Protected Routes
201
+
202
+ Routes are protected by default. Make a route public:
203
+
204
+ ```typescript
205
+ @Post('/auth/login', { isProtected: false })
206
+ export class Login implements RabApiPost<ControllerT> {
207
+ // Public endpoint
208
+ }
209
+ ```
210
+
211
+ Access authenticated user:
212
+
213
+ ```typescript
214
+ @Get('/profile')
215
+ export class GetProfile implements RabApiGet<ControllerT> {
216
+ handler: ControllerT['request'] = async (request) => {
217
+ const user = request.auth; // JWT payload
218
+ return { userId: user.userId };
219
+ };
220
+ }
221
+ ```
222
+
223
+ ## Authorization
224
+
225
+ Use permission-based access control:
226
+
227
+ ```typescript
228
+ @Post('/admin/users', { permission: 'canCreateUser' })
229
+ export class CreateUser implements RabApiPost<ControllerT> {
230
+ // Only users with 'canCreateUser' permission
231
+ }
232
+ ```
233
+
234
+ Integrate with `@softin/rab-access`:
235
+
236
+ ```typescript
237
+ import { Rab } from '@softin/rab-access';
238
+
239
+ const permissions = Rab.schema({
240
+ canCreateUser: [Rab.grant('admin'), Rab.grant('superAdmin')],
241
+ canDeleteUser: [Rab.grant('superAdmin')],
242
+ });
243
+ ```
244
+
245
+ ## Error Handling
246
+
247
+ Built-in exceptions:
248
+
249
+ ```typescript
250
+ import {
251
+ BadRequestException,
252
+ UnauthorizedException,
253
+ ForbiddenException,
254
+ NotFoundException,
255
+ ConflictException,
256
+ } from 'rab-api';
257
+
258
+ @Get('/users/:id')
259
+ export class GetUser implements RabApiGet<ControllerT> {
260
+ handler: ControllerT['request'] = async (request) => {
261
+ const user = await findUser(request.params.id);
262
+ if (!user) throw new NotFoundException('User not found');
263
+ return user;
264
+ };
265
+ }
266
+ ```
267
+
268
+ Custom error handler:
269
+
270
+ ```typescript
271
+ const app = RabApi.createApp({
272
+ errorHandler: (err, req, res, next) => {
273
+ if (err instanceof RabApiError) {
274
+ return res.status(err.statusCode).json({ error: err.message });
275
+ }
276
+ return res.status(500).json({ error: 'Internal error' });
277
+ },
278
+ });
279
+ ```
280
+
281
+ ## Middleware
282
+
283
+ Apply middleware at different levels:
284
+
285
+ ```typescript
286
+ // Route level
287
+ @Get('/users', { pipes: [loggerMiddleware] })
288
+ export class ListUsers {}
289
+
290
+ // Router level
291
+ app.route({
292
+ basePath: '/api',
293
+ pipes: [corsMiddleware, loggerMiddleware],
294
+ controllers: [/* ... */],
295
+ });
296
+
297
+ // Conditional
298
+ const conditionalAuth = (route) => {
299
+ return route.isProtected ? [authMiddleware] : [];
300
+ };
301
+
302
+ app.route({
303
+ pipes: [conditionalAuth],
304
+ controllers: [/* ... */],
305
+ });
306
+ ```
307
+
308
+ ## Advanced Features
309
+
310
+ ### Controller Type Helpers
311
+
312
+ ```typescript
313
+ // Type helpers for controllers
314
+ PostController<TBody, TResponse, TParams?, TUser?, TQuery?>
315
+ GetController<TResponse, TQuery?, TParams?, TUser?>
316
+ PutController<TBody, TResponse, TParams?, TUser?, TQuery?>
317
+ PatchController<TBody, TResponse, TParams?, TUser?, TQuery?>
318
+ DeleteController<TParams?, TUser?>
319
+
320
+ // Interface implementations
321
+ RabApiPost<T> | AtomApiPost<T>
322
+ RabApiGet<T> | AtomApiGet<T>
323
+ RabApiPut<T> | AtomApiPut<T>
324
+ RabApiPatch<T> | AtomApiPatch<T>
325
+ RabApiDelete<T> | AtomApiDelete<T>
326
+ ```
327
+
328
+ ## Route Options
329
+
330
+ ```typescript
331
+ interface RouteOptions {
332
+ bodySchema?: Joi.ObjectSchema; // Body validation
333
+ querySchema?: Joi.ObjectSchema; // Query validation
334
+ isProtected?: boolean; // JWT required (default: true)
335
+ permission?: string; // Permission name
336
+ pipes?: Function[]; // Middleware
337
+ excludeFromDocs?: boolean; // Hide from OpenAPI
338
+ tags?: string[]; // OpenAPI tags
339
+ }
340
+ ```
341
+
342
+ ## Complete Example
343
+
344
+ ```typescript
345
+ import { Get, Post, Put, Delete, RabApi, Injectable } from 'rab-api';
346
+ import * as Joi from 'joi';
347
+
348
+ // Service
349
+ @Injectable()
350
+ class ProductService {
351
+ async findAll() { return []; }
352
+ async create(data: any) { return { id: '1', ...data }; }
353
+ }
354
+
355
+ // Controllers
356
+ const createSchema = Joi.object({
357
+ name: Joi.string().required(),
358
+ price: Joi.number().required(),
359
+ });
360
+
361
+ @Get('/')
362
+ class ListProducts {
363
+ constructor(private service: ProductService) {}
364
+ handler = async () => await this.service.findAll();
365
+ }
366
+
367
+ @Post('/', { bodySchema: createSchema })
368
+ class CreateProduct {
369
+ constructor(private service: ProductService) {}
370
+ handler = async (req) => await this.service.create(req.body);
371
+ }
372
+
373
+ // App
374
+ const app = RabApi.createApp({
375
+ auth: { jwt: { secret_key: 'secret', algorithms: ['HS256'] } },
376
+ });
377
+
378
+ app.use(express.json());
379
+ app.route({
380
+ basePath: '/products',
381
+ controllers: [ListProducts, CreateProduct],
382
+ });
383
+
384
+ app.listen(3000);
385
+ ```
386
+
387
+ ## License
388
+
389
+ MIT © Softin Hub
390
+
391
+ ## Support
392
+
393
+ Email: softin.developer@gmail.com
package/index.cjs.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/index";