speedly 2.0.38 → 2.0.42

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.
Files changed (77) hide show
  1. package/README.md +470 -171
  2. package/dist/cjs/config/init.js +2 -2
  3. package/dist/cjs/kit/db/db.js +16 -0
  4. package/dist/cjs/model/translation.d.ts +37 -25
  5. package/dist/cjs/util/translator.d.ts +1 -1
  6. package/dist/config/init.d.ts +23 -0
  7. package/dist/config/init.js +93 -0
  8. package/dist/document/document.d.ts +5 -0
  9. package/dist/document/document.js +270 -0
  10. package/dist/document/index.d.ts +2 -0
  11. package/dist/document/index.js +7 -0
  12. package/dist/document/parser.d.ts +1 -0
  13. package/dist/document/parser.js +11 -0
  14. package/dist/esm/config/init.js +2 -2
  15. package/dist/esm/kit/db/db.js +16 -0
  16. package/dist/esm/model/translation.d.ts +37 -25
  17. package/dist/esm/util/translator.d.ts +1 -1
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.js +13 -0
  20. package/dist/{cjs → kit}/auth/auth.js +1 -1
  21. package/dist/{cjs → kit}/db/db.d.ts +15 -0
  22. package/dist/{cjs → kit}/db/db.js +44 -19
  23. package/dist/kit/index.d.ts +5 -0
  24. package/dist/kit/index.js +14 -0
  25. package/dist/{cjs → kit}/uploader/uploader.d.ts +2 -2
  26. package/dist/{esm → kit}/uploader/uploader.js +22 -17
  27. package/dist/model/index.d.ts +2 -0
  28. package/dist/model/index.js +8 -0
  29. package/dist/model/translation.d.ts +71 -0
  30. package/dist/model/translation.js +13 -0
  31. package/dist/modules/index.d.ts +2 -0
  32. package/dist/modules/index.js +8 -0
  33. package/dist/modules/translation/translation.routes.d.ts +2 -0
  34. package/dist/modules/translation/translation.routes.js +24 -0
  35. package/dist/modules/translation/translation.validator.d.ts +15 -0
  36. package/dist/modules/translation/translation.validator.js +22 -0
  37. package/dist/util/getConfig.d.ts +4 -0
  38. package/dist/util/getConfig.js +40 -0
  39. package/dist/util/index.d.ts +2 -0
  40. package/dist/util/index.js +8 -0
  41. package/dist/util/makeOptional.d.ts +10 -0
  42. package/dist/util/makeOptional.js +47 -0
  43. package/dist/util/strToObj.d.ts +2 -0
  44. package/dist/util/strToObj.js +9 -0
  45. package/dist/util/translator.d.ts +2 -0
  46. package/dist/util/translator.js +74 -0
  47. package/examples/blog-routes/blog/blog.routes.js +15 -0
  48. package/examples/blog-routes/blog/blog.validator.js +35 -0
  49. package/examples/blog-routes/role/role.routes.js +14 -0
  50. package/examples/blog-routes/role/role.validator.js +28 -0
  51. package/examples/blog-routes/user/user.controller.js +97 -0
  52. package/examples/blog-routes/user/user.routes.js +18 -0
  53. package/examples/blog-routes/user/user.validator.js +53 -0
  54. package/package.json +65 -66
  55. package/dist/cjs/auth/auth2.d.ts +0 -18
  56. package/dist/cjs/auth/auth2.js +0 -93
  57. package/dist/cjs/uploader/uploader.js +0 -145
  58. package/dist/cjs/yup.config.d.ts +0 -2
  59. package/dist/cjs/yup.config.js +0 -24
  60. package/dist/esm/auth/auth.d.ts +0 -3
  61. package/dist/esm/auth/auth.js +0 -38
  62. package/dist/esm/auth/types.d.ts +0 -19
  63. package/dist/esm/auth/types.js +0 -2
  64. package/dist/esm/db/db.d.ts +0 -182
  65. package/dist/esm/db/db.js +0 -594
  66. package/dist/esm/db/utils.d.ts +0 -3
  67. package/dist/esm/db/utils.js +0 -15
  68. package/dist/esm/uploader/uploader.d.ts +0 -24
  69. package/dist/esm/validator/validator.d.ts +0 -9
  70. package/dist/esm/validator/validator.js +0 -36
  71. /package/dist/{cjs → kit}/auth/auth.d.ts +0 -0
  72. /package/dist/{cjs → kit}/auth/types.d.ts +0 -0
  73. /package/dist/{cjs → kit}/auth/types.js +0 -0
  74. /package/dist/{cjs → kit}/db/utils.d.ts +0 -0
  75. /package/dist/{cjs → kit}/db/utils.js +0 -0
  76. /package/dist/{cjs → kit}/validator/validator.d.ts +0 -0
  77. /package/dist/{cjs → kit}/validator/validator.js +0 -0
package/README.md CHANGED
@@ -1,171 +1,470 @@
1
- **Overview**
2
-
3
- - **Project**: `speedly` — a lightweight express utility framework that bundles auth middlewares, database model handlers, file uploader helpers, request validators, API documentation loader and small utilities to speed up building REST APIs.
4
- - **Entry point exports**: `auth`, `db`, `uploader`, `validator`, `models`, `modules`, `utils`, `document` (see below for details and examples).
5
-
6
- **Quick Start**
7
-
8
- - **Install**: add the project to your workspace or import the package in your service.
9
- - **Basic server skeleton**:
10
-
11
- ```
12
- import express from 'express'
13
- import { auth, db, uploader, validator, models, modules, utils, document } from './src'
14
-
15
- const app = express();
16
- app.use(express.json());
17
-
18
- // Mount example module router
19
- app.use('/api/translation', modules.translation);
20
-
21
- // Swagger docs (served at /docs)
22
- document(app, require('path').join(process.cwd(), 'src/modules'));
23
-
24
- app.listen(3000);
25
- ```
26
-
27
- **Exports Reference**
28
-
29
- **`auth`**
30
-
31
- - **Type**: default export (object)
32
- - **Purpose**: Provides simple express middlewares for access control. Each function returns an Express middleware that inspects the request using a configurable `customValidator` (from `getConfig('auth')`) and either allows, forbids or rejects the request.
33
- - **API**:
34
- - `auth.user()` → middleware that enforces a `user` access type.
35
- - `auth.admin(config?)` → middleware for admin access. Optionally pass `{ permission }` to require a specific admin permission (the middleware names themselves include the permission, e.g. `auth:admin:PERM`).
36
- - `auth.any()` → middleware that accepts any authenticated-type logic configured by the `customValidator`.
37
- - **Notes**: `auth` reads default options from `getConfig('auth')`. The `customValidator` should return truthy to allow or falsy to forbid; `null` is treated as unauthorized.
38
-
39
- **`db`**
40
-
41
- - **Type**: default export (factory function)
42
- - **Purpose**: Creates Express middlewares that operate on a Mongoose model (or other model loaded from the `models` path). Designed to simplify CRUD endpoints by composing handler builders like `.find()`, `.create()`, `.findByIdAndUpdate()`, etc.
43
- - **How to use**: call `db(collectionName, config?)` to get a model handler factory, then chain action methods and use the returned function as an Express middleware.
44
- - **Common methods returned by `db('collection')`**:
45
- - `.find(match = {})`
46
- - `.create(body = {})`
47
- - `.updateOne(match, body)`
48
- - `.updateMany(match, body)`
49
- - `.deleteOne(match)`
50
- - `.deleteMany(match)`
51
- - `.findOne(match)`
52
- - `.findOneAndUpdate(match, body)`
53
- - `.aggregate(pipeline)`
54
- - `.findById(id)`
55
- - `.findByIdAndUpdate(id, body)`
56
- - `.findByIdAndDelete(id)`
57
- - **Query behavior**: The produced middleware reads query params like `search`, `filters`, `sort`, `page`, `limit`, and `select` to modify results. Pagination behaviour can be controlled via `getConfig('db')` (e.g., `pagination.quantity`, `pagination.detailed`).
58
- - **Config**: second argument allows overriding `{ path, type: 'internal'|'external', message }`. When `type` is `internal` the loader resolves models relative to the library; when `external` it resolves from the host app and `configs.path`.
59
- - **Example**:
60
-
61
- ```
62
- // GET /api/translation -> finds translations
63
- app.get('/api/translation', db('translation', { type: 'internal' }).find());
64
-
65
- // POST /api/translation -> create translation documents
66
- app.post('/api/translation', db('translation', { type: 'internal' }).create());
67
- ```
68
-
69
- **`uploader`**
70
-
71
- - **Type**: default export (factory)
72
- - **Purpose**: Provides file uploading middlewares built on `multer` and convenience helpers that optionally save media metadata to a `media` collection.
73
- - **Signature**: `uploader(destination = '/image', config?)` returns an object with methods `{ single, array, fields, any, none }` which are wrappers around multer handlers.
74
- - **Config options** (defaults obtained from `getConfig('uploader')`):
75
- - `saveInDb` (boolean) — whether to persist metadata in a `media` collection
76
- - `prefix` (string) — prefix for saved filenames
77
- - `limit` (number) — max upload size in MB
78
- - `format` (RegExp) — allowed file extensions
79
- - `path` (string) base path to save files (default `../../../public` in library defaults)
80
- - **Returned helpers**:
81
- - `single(fieldName)` middleware saving a single file and setting the file URL into `req.body[fieldName]`. If `saveInDb` is true it will store a doc in `media` and set `req.mediaId`.
82
- - `array(fieldName, maxCount)` — accept multiple files and set an array of URLs into `req.body[fieldName]`.
83
- - `fields(fieldsArray)` — accept mixed fields (multer-style field definitions).
84
- - `any()` and `none()` — passthrough multer helpers.
85
- - **Example**:
86
-
87
- ```
88
- app.post('/upload', uploader('/images').single('photo'), (req,res) => {
89
- res.json({ url: req.body.photo });
90
- });
91
- ```
92
-
93
- **`validator`**
94
-
95
- - **Type**: default export (generic factory)
96
- - **Purpose**: A small wrapper around `yup` to validate `req.body`, `req.params`, and `req.query`. On failure it forwards an error object to `next()` with `status: 405` and a message.
97
- - **Signature**: `validator({ body?: yup.Schema, params?: yup.Schema, query?: yup.Schema })` returns an Express `RequestHandler`.
98
- - **Behavior**: strips unknown fields, assigns validated values back to `req.body`, `req.params`, and `req.query`. The created middleware is annotated with `__validationSchema` (used by automatic documentation generator).
99
- - **Example**:
100
-
101
- ```
102
- import * as yup from 'yup';
103
- const schema = { body: yup.object({ text: yup.string().required() }) };
104
- app.post('/translate', validator(schema), handler);
105
- ```
106
-
107
- **`models`**
108
-
109
- - **Type**: object containing Mongoose models
110
- - **Currently included**:
111
- - `translation`Mongoose model with fields `{ text: String, lang: String, translatedText: String }` and timestamps.
112
- - **Purpose**: Direct access to low-level models for custom operations (e.g., prefetch, caching or complex queries).
113
-
114
- **`modules`**
115
-
116
- - **Type**: object of `express.Router` instances keyed by module name
117
- - **Included**:
118
- - `translation` — router defined in `src/modules/translation/translation.routes.ts` with routes:
119
- - `GET /` → `db('translation', {type:'internal'}).find()`
120
- - `POST /` → guarded by a simple body `auth` check inside the route and then `create()`
121
- - `PUT /:id` `auth.admin()` + `validator(...)` + `findByIdAndUpdate()`
122
- - **Mounting**: `app.use('/api/translation', modules.translation)`
123
-
124
- **`utils`**
125
-
126
- - **Type**: object of helper utilities
127
- - **Included**:
128
- - `translator` — a small translation helper that attempts multiple external translation providers and caches successful translations to the `translation` model. Signature: `translator(text = 'unspecified text', lang = 'fa') => Promise<string>`.
129
- - **Behavior**: Normalizes text, looks up local cache (`translation` model), attempts external services (a worker proxy and optionally `one-api`), writes the result to DB, and returns the translated text. Falls back to the formatted original text on failure.
130
-
131
- **`document`**
132
-
133
- - **Type**: default export (function)
134
- - **Purpose**: Scans `src/modules` routers and mounts a Swagger UI at `/docs` with automatically generated OpenAPI paths and basic security scheme.
135
- - **Signature**: `document(app: Express, baseDir?: string)` — `baseDir` defaults to `path.join(process.cwd(), 'src/module')` in the loader. Use a correct path to your modules folder (e.g., `path.join(process.cwd(), 'src/modules')`).
136
- - **What it detects**: routes, http methods, `yup` validation schemas (to document request bodies), and auth middlewares (to add security hints and descriptions).
137
-
138
- **Configuration & Environment**
139
-
140
- - Use `getConfig(key)` (internal) to supply runtime options for `auth`, `db`, `uploader`, and `translate` providers. Typical environment keys used by the modules:
141
- - `JWT_KEY` (used by auth-related configs)
142
- - `DB_URL` (database connection string if using external db config)
143
- - `one_api_token` (optional token for the alternate translator provider)
144
-
145
- **Examples**
146
-
147
- - Mounting everything in a small app:
148
-
149
- ```
150
- import express from 'express';
151
- import { modules, document } from './src';
152
- import path from 'path';
153
-
154
- const app = express();
155
- app.use(express.json());
156
- app.use('/api/translation', modules.translation);
157
- document(app, path.join(process.cwd(), 'src/modules'));
158
- app.listen(3000);
159
- ```
160
-
161
- **Developer Notes**
162
-
163
- - `db` middleware attaches pagination, `search` and `filters` behavior using `req.query`. Use `type: 'internal'` when you want the model path resolved inside the package, or `external` to resolve from the consumer app.
164
- - `validator` attaches `__validationSchema` to middlewares which `document` uses to generate OpenAPI schemas.
165
- - `uploader` writes files to disk under the configured `path` and returns public URLs prefixed with `/static`.
166
-
167
- If you'd like, I can also:
168
-
169
- - add usage examples/tests around each exported function
170
- - add API reference tables per-method for `db` handlers
171
- - generate a small example express app under `examples/` using these exports
1
+ # speedly
2
+
3
+ A lightweight Express utility framework that bundles auth middlewares, database model handlers, file uploader helpers, request validators, an API documentation loader, and small utilities to speed up building REST APIs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install speedly
9
+ ```
10
+
11
+ Install peer dependencies (Express, Mongoose, and Multer are required):
12
+
13
+ ```bash
14
+ npm install express mongoose multer
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```js
20
+ const speedly = require('speedly');
21
+ const mongoose = require('mongoose');
22
+
23
+ const app = speedly();
24
+
25
+ mongoose.connect('mongodb://localhost:27017/myapp')
26
+ .catch(err => { console.error('MongoDB connection error:', err); process.exit(1); });
27
+
28
+ app.listen(3000, () => {
29
+ console.log('Server running on http://localhost:3000');
30
+ console.log('API docs at http://localhost:3000/docs');
31
+ });
32
+ ```
33
+
34
+ Or with ESM:
35
+
36
+ ```js
37
+ import speedly from 'speedly';
38
+ import mongoose from 'mongoose';
39
+
40
+ const app = speedly();
41
+
42
+ await mongoose.connect('mongodb://localhost:27017/myapp');
43
+
44
+ app.listen(3000);
45
+ ```
46
+
47
+ `speedly()` returns a fully configured Express app with JSON parsing, URL-encoded body parsing, static file serving, a home route, a 404 handler, an error handler, and Swagger documentation pre-wired.
48
+
49
+ ### Init options
50
+
51
+ ```js
52
+ const app = speedly({
53
+ jsonParser: true, // default: true
54
+ urlEncodedParser: true, // default: true
55
+ cookieParser: true, // default: true
56
+ staticFiles: true, // default: true — serves /public at /static
57
+ homeHandler: true, // default: true GET / returns a welcome page
58
+ notFoundHandler: true, // default: true
59
+ errorHandler: true, // default: true
60
+ documentation: true, // default: true — mounts Swagger UI at /docs
61
+ });
62
+ ```
63
+
64
+ ---
65
+
66
+ ## API Reference
67
+
68
+ ### `speedly/kit` — auth, db, uploader, validator
69
+
70
+ ```js
71
+ const { auth, db, uploader, validator } = require('speedly/kit');
72
+ ```
73
+
74
+ #### `auth`
75
+
76
+ Express middlewares for access control.
77
+
78
+ ```js
79
+ const { auth } = require('speedly/kit');
80
+
81
+ // Require an authenticated user
82
+ app.get('/profile', auth.user(), handler);
83
+
84
+ // Require an admin
85
+ app.delete('/post/:id', auth.admin(), handler);
86
+
87
+ // Require an admin with a specific permission
88
+ app.get('/users', auth.admin({ permission: 'OWNER' }), handler);
89
+
90
+ // Accept any authenticated request
91
+ app.get('/dashboard', auth.any(), handler);
92
+ ```
93
+
94
+ - `auth.user()` — enforces user-level authentication.
95
+ - `auth.admin(config?)` enforces admin access. Pass `{ permission }` to require a specific permission string (e.g. `'OWNER'`, `'ADMIN'`).
96
+ - `auth.any()` accepts any authenticated request regardless of role.
97
+
98
+ #### `db`
99
+
100
+ Creates Express route handlers that run Mongoose operations on a model. Supports pagination, search, sort, and field selection via query params (`?search=`, `?page=`, `?limit=`, `?sort=`, `?select=`).
101
+
102
+ ```js
103
+ const { db } = require('speedly/kit');
104
+
105
+ // GET /posts — list all posts (supports ?search=, ?page=, ?limit=)
106
+ app.get('/posts', db('post').find());
107
+
108
+ // POST /posts — create a new post
109
+ app.post('/posts', db('post').create());
110
+
111
+ // GET /posts/:id get post by id
112
+ app.get('/posts/:id', db('post').findById());
113
+
114
+ // PUT /posts/:id — update post by id
115
+ app.put('/posts/:id', db('post').findByIdAndUpdate());
116
+
117
+ // DELETE /posts/:id — delete post by id
118
+ app.delete('/posts/:id', db('post').findByIdAndDelete());
119
+ ```
120
+
121
+ Available methods: `.find()`, `.create()`, `.findOne()`, `.findById()`, `.findByIdAndUpdate()`, `.findByIdAndDelete()`, `.updateOne()`, `.updateMany()`, `.deleteOne()`, `.deleteMany()`, `.findOneAndUpdate()`, `.aggregate()`.
122
+
123
+ You can pass a callback to build the query dynamically from `req`:
124
+
125
+ ```js
126
+ // GET /posts/:slug find by slug and increment view count
127
+ app.get('/posts/:slug',
128
+ db('post').findOneAndUpdate(
129
+ req => ({ slug: req.params.slug }, { $inc: { views: 1 } })
130
+ ).populate('author_id')
131
+ );
132
+
133
+ // POST /posts create with author set from authenticated user
134
+ app.post('/posts',
135
+ auth.admin(),
136
+ db('post').create(req => ({ author_id: req.user._id }))
137
+ );
138
+ ```
139
+
140
+ Chain `.populate()` and `.select()` to control what is returned:
141
+
142
+ ```js
143
+ app.get('/posts',
144
+ db('post').find()
145
+ .populate([{ path: 'author_id', select: '-password' }, 'thumbnail_id'])
146
+ );
147
+
148
+ app.get('/me',
149
+ auth.user(),
150
+ db('user').findOne(req => ({ _id: req.user._id }))
151
+ .populate('role_id')
152
+ .select('-password')
153
+ );
154
+ ```
155
+
156
+ Pass `{ type: 'internal' }` to resolve speedly's built-in models:
157
+
158
+ ```js
159
+ app.get('/translations', db('translation', { type: 'internal' }).find());
160
+ ```
161
+
162
+ #### `uploader`
163
+
164
+ File upload middlewares built on `multer`, with optional metadata persistence to a `media` collection.
165
+
166
+ ```js
167
+ const { uploader } = require('speedly/kit');
168
+
169
+ // Single file upload
170
+ app.post('/upload', uploader('/images').single('photo'), (req, res) => {
171
+ res.json({ url: req.body.photo });
172
+ });
173
+
174
+ // Multiple files
175
+ app.post('/gallery', uploader('/images').array('photos', 10), (req, res) => {
176
+ res.json({ urls: req.body.photos });
177
+ });
178
+ ```
179
+
180
+ Config options: `saveInDb`, `prefix`, `limit` (MB), `format` (RegExp), `path`.
181
+
182
+ #### `validator`
183
+
184
+ Request validation middleware built on `yup`. Validates `req.body`, `req.params`, and `req.query`.
185
+
186
+ ```js
187
+ const { validator } = require('speedly/kit');
188
+ const yup = require('yup');
189
+
190
+ app.post(
191
+ '/posts',
192
+ validator({
193
+ body: yup.object({
194
+ title: yup.string().required(),
195
+ slug: yup.string().required(),
196
+ status: yup.string().oneOf(['draft', 'published', 'hidden']),
197
+ }),
198
+ }),
199
+ handler
200
+ );
201
+
202
+ // Validate route params
203
+ app.put(
204
+ '/posts/:id',
205
+ validator({
206
+ params: yup.object({ id: yup.string().required() }),
207
+ body: yup.object({ title: yup.string() }),
208
+ }),
209
+ handler
210
+ );
211
+ ```
212
+
213
+ On validation failure, calls `next({ status: 405, message: '...' })`.
214
+
215
+ ---
216
+
217
+ ### `speedly/modules` — built-in routers
218
+
219
+ ```js
220
+ const { translation } = require('speedly/modules');
221
+
222
+ app.use('/api/translation', translation);
223
+ ```
224
+
225
+ Includes a `translation` router with:
226
+ - `GET /` — list translations (supports `?search=`, `?page=`, `?limit=`)
227
+ - `POST /` — create a translation
228
+ - `PUT /:id` — update a translation (requires admin auth)
229
+
230
+ ---
231
+
232
+ ### `speedly/document` — Swagger UI
233
+
234
+ Scans your module routers and mounts a Swagger UI at `/docs`.
235
+
236
+ ```js
237
+ const document = require('speedly/document');
238
+ const path = require('path');
239
+
240
+ document(app, path.join(process.cwd(), 'src/modules'));
241
+ // Visit http://localhost:3000/docs
242
+ ```
243
+
244
+ Automatically detects routes, HTTP methods, `yup` validation schemas, and auth middlewares to generate OpenAPI documentation.
245
+
246
+ ---
247
+
248
+ ### `speedly/util` — utilities
249
+
250
+ ```js
251
+ const { translator } = require('speedly/util');
252
+
253
+ const result = await translator('Hello', 'fa');
254
+ console.log(result); // translated text
255
+ ```
256
+
257
+ `translator(text, lang)` — translates text to the target language, caching results in the `translation` model.
258
+
259
+ ---
260
+
261
+ ### `speedly/model` — Mongoose models
262
+
263
+ ```js
264
+ const { translation } = require('speedly/model');
265
+
266
+ const docs = await translation.find({ lang: 'fa' });
267
+ ```
268
+
269
+ Includes the `translation` model with fields: `text`, `lang`, `translatedText`, and timestamps.
270
+
271
+ ---
272
+
273
+ ## Examples
274
+
275
+ ### Blog API
276
+
277
+ A full blog REST API with auth-protected write routes and public read routes.
278
+
279
+ **`src/modules/blog/blog.validator.js`**
280
+
281
+ ```js
282
+ const { validator } = require('speedly/kit');
283
+ const yup = require('yup');
284
+
285
+ const paramId = yup.object({
286
+ id: yup.string().required('id is required'),
287
+ });
288
+
289
+ const schema = yup.object({
290
+ title: yup.string().required(),
291
+ slug: yup.string().required().matches(/^[\d\w-]+$/i, 'slug can only contain letters, numbers and dashes'),
292
+ subTitle: yup.string(),
293
+ content: yup.string(),
294
+ status: yup.string().oneOf(['draft', 'published', 'hidden']),
295
+ });
296
+
297
+ exports.post = { body: schema };
298
+ exports.put = { params: paramId, body: schema };
299
+ exports.delete = { params: paramId };
300
+ ```
301
+
302
+ **`src/modules/blog/blog.routes.js`**
303
+
304
+ ```js
305
+ const express = require('express');
306
+ const { auth, db, validator } = require('speedly/kit');
307
+ const v = require('./blog.validator');
308
+
309
+ const router = express.Router();
310
+
311
+ // Public: list all posts with author and thumbnail populated
312
+ router.get('/',
313
+ db('blog').find()
314
+ .populate([{ path: 'author_id', select: '-password' }, 'thumbnail_id'])
315
+ );
316
+
317
+ // Public: get single post by slug and increment view count
318
+ router.get('/:slug',
319
+ db('blog').findOneAndUpdate(
320
+ req => ({ slug: req.params.slug }, { $inc: { views: 1 } })
321
+ ).populate([{ path: 'author_id', select: '-password' }, 'thumbnail_id'])
322
+ );
323
+
324
+ // Admin only: create / update / delete
325
+ router.post('/', auth.admin(), validator(v.post), db('blog').create(req => ({ author_id: req.user._id })));
326
+ router.put('/:id', auth.admin(), validator(v.put), db('blog').findByIdAndUpdate());
327
+ router.delete('/:id', auth.admin(), validator(v.delete), db('blog').findByIdAndDelete());
328
+
329
+ module.exports = router;
330
+ ```
331
+
332
+ ### User & Auth Routes
333
+
334
+ Registration, login, and profile routes with OTP flow.
335
+
336
+ **`src/modules/user/user.routes.js`**
337
+
338
+ ```js
339
+ const express = require('express');
340
+ const { auth, db, validator } = require('speedly/kit');
341
+ const yup = require('yup');
342
+
343
+ const router = express.Router();
344
+
345
+ const phoneSchema = yup.object({
346
+ // Iranian mobile format: 11 digits starting with 09 — adjust regex for your region
347
+ phone: yup.string().required().matches(/^09\d{9}$/, 'Enter an 11-digit phone number starting with 09'),
348
+ });
349
+
350
+ // Check phone number and send OTP
351
+ router.post('/check-phone',
352
+ validator({ body: phoneSchema }),
353
+ myUserController.checkPhone // your custom controller
354
+ );
355
+
356
+ // Register / login with OTP
357
+ router.post('/register',
358
+ validator({ body: phoneSchema }),
359
+ myUserController.register
360
+ );
361
+
362
+ // Get current user profile
363
+ router.get('/me',
364
+ auth.user(),
365
+ db('user').findOne(req => ({ _id: req.user._id }))
366
+ .populate('role_id')
367
+ .select('-password')
368
+ );
369
+
370
+ // Admin: list all users
371
+ router.get('/',
372
+ auth.admin({ permission: 'OWNER' }),
373
+ db('user').find()
374
+ );
375
+
376
+ // Admin: create / update / delete user
377
+ router.post('/', auth.admin(), db('user').create());
378
+ router.put('/:id', auth.admin(), db('user').findByIdAndUpdate());
379
+ router.delete('/:id', auth.admin(), db('user').findByIdAndDelete());
380
+
381
+ module.exports = router;
382
+ ```
383
+
384
+ ### Role Management
385
+
386
+ CRUD routes for roles, restricted to users with the `OWNER` permission.
387
+
388
+ **`src/modules/role/role.routes.js`**
389
+
390
+ ```js
391
+ const express = require('express');
392
+ const { auth, db, validator } = require('speedly/kit');
393
+ const yup = require('yup');
394
+
395
+ const router = express.Router();
396
+
397
+ const schema = yup.object({
398
+ title: yup.string().required(),
399
+ access: yup.string().required().oneOf(['OWNER', 'ADMIN', 'EXPERT']),
400
+ });
401
+
402
+ router.get('/', auth.admin({ permission: 'OWNER' }), db('role').find());
403
+ router.post('/', auth.admin({ permission: 'OWNER' }), validator({ body: schema }), db('role').create());
404
+ router.put('/:id', auth.admin({ permission: 'OWNER' }), validator({ body: schema }), db('role').findByIdAndUpdate());
405
+ router.delete('/:id', auth.admin({ permission: 'OWNER' }), db('role').findByIdAndDelete());
406
+
407
+ module.exports = router;
408
+ ```
409
+
410
+ ### File Upload
411
+
412
+ ```js
413
+ const express = require('express');
414
+ const { auth, uploader } = require('speedly/kit');
415
+
416
+ const router = express.Router();
417
+
418
+ // Single image upload (saves file info to the media collection)
419
+ router.post('/image',
420
+ auth.user(),
421
+ uploader('/images', { saveInDb: true, limit: 5 /* max file size in MB */, format: /image\/(jpeg|png|webp)/ }).single('photo'),
422
+ (req, res) => res.json({ url: req.body.photo })
423
+ );
424
+
425
+ // Multiple file upload
426
+ router.post('/gallery',
427
+ auth.user(),
428
+ uploader('/images').array('photos', 10),
429
+ (req, res) => res.json({ urls: req.body.photos })
430
+ );
431
+
432
+ module.exports = router;
433
+ ```
434
+
435
+ ### Putting It All Together
436
+
437
+ **`src/app.js`**
438
+
439
+ ```js
440
+ const speedly = require('speedly');
441
+ const mongoose = require('mongoose');
442
+ const { translation } = require('speedly/modules');
443
+
444
+ const blogRoutes = require('./modules/blog/blog.routes');
445
+ const userRoutes = require('./modules/user/user.routes');
446
+ const roleRoutes = require('./modules/role/role.routes');
447
+
448
+ const app = speedly();
449
+
450
+ mongoose.connect(process.env.MONGO_URI || 'mongodb://localhost:27017/myapp');
451
+
452
+ // Built-in translation module
453
+ app.use('/api/translation', translation);
454
+
455
+ // Application routes
456
+ app.use('/api/blogs', blogRoutes);
457
+ app.use('/api/users', userRoutes);
458
+ app.use('/api/roles', roleRoutes);
459
+
460
+ app.listen(3000, () => {
461
+ console.log('Running on http://localhost:3000');
462
+ console.log('API docs: http://localhost:3000/docs');
463
+ });
464
+ ```
465
+
466
+ ---
467
+
468
+ ## License
469
+
470
+ MIT
@@ -47,8 +47,8 @@ function speedly(config = {}) {
47
47
  app.use("/static", express_1.default.static("public"));
48
48
  if (finalConfig.homeHandler) {
49
49
  app.get("/", (req, res) => {
50
- res.send(`<h1>Welcome to ${require(path_1.default.join(process.cwd(), "package.json")).name} App</h1>
51
- <p>Your app is running successfully.</p>
50
+ res.send(`<h1>Welcome to ${require(path_1.default.join(process.cwd(), "package.json")).name} App</h1>
51
+ <p>Your app is running successfully.</p>
52
52
  ${finalConfig.documentation
53
53
  ? '<p>Visit <a href="/docs">/docs</a> for API documentation.</p>'
54
54
  : ""}`);