nexus-backend 1.1.1 → 1.1.3
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 +522 -349
- package/dist/index.d.mts +2 -22
- package/dist/index.d.ts +2 -22
- package/dist/index.js +7 -52
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +35 -35
package/README.md
CHANGED
|
@@ -1,349 +1,522 @@
|
|
|
1
|
-
# Nexus Backend
|
|
2
|
-
|
|
3
|
-
A lightweight backend utility library for [Express.js](https://expressjs.com/) providing structured error
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Table of Contents
|
|
8
|
-
|
|
9
|
-
- [Installation](#installation)
|
|
10
|
-
- [Quick Start](#quick-start)
|
|
11
|
-
- [API Reference](#api-reference)
|
|
12
|
-
- [Response Helpers](#response-helpers)
|
|
13
|
-
- [
|
|
14
|
-
- [Error Middleware](#error-middleware)
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
// src/
|
|
54
|
-
import
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
```ts
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
throw new
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
"
|
|
298
|
-
|
|
299
|
-
"
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
1
|
+
# Nexus Backend (A sub project of Nexus JS)
|
|
2
|
+
|
|
3
|
+
A lightweight backend utility library for [Express.js](https://expressjs.com/) providing a structured error class, standardised API response helpers, plug-and-play error handling middleware, MongoDB connection helpers, in-memory caching, email sending, and file upload utilities.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [API Reference](#api-reference)
|
|
12
|
+
- [Response Helpers](#response-helpers)
|
|
13
|
+
- [NexusError](#nexuserror)
|
|
14
|
+
- [Error Middleware](#error-middleware)
|
|
15
|
+
- [MongoDB Connection](#mongodb-connection)
|
|
16
|
+
- [Caching](#caching)
|
|
17
|
+
- [Environment Variables](#environment-variables)
|
|
18
|
+
- [Random Number Generator](#random-number-generator)
|
|
19
|
+
- [Mailer](#mailer)
|
|
20
|
+
- [File Uploads (Nexus Cloud)](#file-uploads-nexus-cloud)
|
|
21
|
+
- [Response Shape](#response-shape)
|
|
22
|
+
- [TypeScript](#typescript)
|
|
23
|
+
- [License](#license)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install nexus-backend
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Depending on which features you use, you'll also need:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Core (always required)
|
|
37
|
+
npm install express mongoose cors helmet morgan cookie-parser
|
|
38
|
+
|
|
39
|
+
# Only if you use Mailer
|
|
40
|
+
npm install resend
|
|
41
|
+
|
|
42
|
+
# Only if you use the Nexus Cloud uploader
|
|
43
|
+
npm install axios form-data multer
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
These are peer dependencies — make sure they're installed in your project.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// src/config/mongoConfig.ts
|
|
54
|
+
import { requiredEnv, type MongoConfig } from "nexus-backend";
|
|
55
|
+
|
|
56
|
+
const dbConfig: MongoConfig = {
|
|
57
|
+
subDomain: requiredEnv(process.env.DB_HOST, "DB_HOST"),
|
|
58
|
+
userName: requiredEnv(process.env.DB_USERNAME, "DB_USERNAME"),
|
|
59
|
+
password: requiredEnv(process.env.DB_PASSWORD, "DB_PASSWORD"),
|
|
60
|
+
cluster: requiredEnv(process.env.DB_CLUSTER, "DB_CLUSTER"),
|
|
61
|
+
dbName: requiredEnv(process.env.DB_NAME, "DB_NAME"),
|
|
62
|
+
shards: [
|
|
63
|
+
requiredEnv(process.env.DB_SHARD_1, "DB_SHARD_1"),
|
|
64
|
+
requiredEnv(process.env.DB_SHARD_2, "DB_SHARD_2"),
|
|
65
|
+
requiredEnv(process.env.DB_SHARD_3, "DB_SHARD_3"),
|
|
66
|
+
],
|
|
67
|
+
replicaSet: requiredEnv(process.env.DB_REPLICA_SET, "DB_REPLICA_SET"),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export default dbConfig;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// src/server.ts
|
|
75
|
+
import express, { type Application, Request, Response } from "express";
|
|
76
|
+
import http from "http";
|
|
77
|
+
import {
|
|
78
|
+
successResponse,
|
|
79
|
+
errorMiddleware,
|
|
80
|
+
NexusError,
|
|
81
|
+
} from "nexus-backend";
|
|
82
|
+
import cors from "cors";
|
|
83
|
+
import helmet from "helmet";
|
|
84
|
+
import morgan from "morgan";
|
|
85
|
+
import cookieParser from "cookie-parser";
|
|
86
|
+
import { Server } from "socket.io";
|
|
87
|
+
|
|
88
|
+
const app: Application = express();
|
|
89
|
+
|
|
90
|
+
if (process.env.NODE_ENV !== "production") {
|
|
91
|
+
app.use(morgan("dev"));
|
|
92
|
+
}
|
|
93
|
+
app.set("trust proxy", 1);
|
|
94
|
+
app.use(helmet());
|
|
95
|
+
app.use(
|
|
96
|
+
cors({
|
|
97
|
+
origin: process.env.FRONTEND_URL,
|
|
98
|
+
credentials: true,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
app.use(express.json({ limit: "500mb" }));
|
|
103
|
+
app.use(express.urlencoded({ extended: true, limit: "500mb" }));
|
|
104
|
+
app.use(cookieParser());
|
|
105
|
+
|
|
106
|
+
// Example route
|
|
107
|
+
app.get("/users/:id", async (req: Request, res: Response) => {
|
|
108
|
+
const user = await getUserById(req.params.id);
|
|
109
|
+
|
|
110
|
+
if (!user) {
|
|
111
|
+
throw new NexusError("User not found", 404);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
res.json(successResponse(user, "User fetched successfully"));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// errorMiddleware is a tuple [routeNotFoundHandler, globalErrorHandler] — spread it.
|
|
118
|
+
// Register it after all routes and other middleware.
|
|
119
|
+
app.use(...errorMiddleware);
|
|
120
|
+
|
|
121
|
+
const server = http.createServer(app);
|
|
122
|
+
export const io = new Server(server, {
|
|
123
|
+
cors: {
|
|
124
|
+
origin: process.env.FRONTEND_URL,
|
|
125
|
+
credentials: true,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export default server;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// src/index.ts
|
|
134
|
+
import "dotenv/config";
|
|
135
|
+
import { connectMongoDb } from "nexus-backend";
|
|
136
|
+
import dns from "dns";
|
|
137
|
+
import dbConfig from "./config/mongoConfig";
|
|
138
|
+
import server from "./server";
|
|
139
|
+
|
|
140
|
+
const PORT = process.env.PORT || 5000;
|
|
141
|
+
|
|
142
|
+
(async () => {
|
|
143
|
+
dns.setServers(["1.1.1.1", "1.0.0.1"]);
|
|
144
|
+
const dbConnected = await connectMongoDb(dbConfig);
|
|
145
|
+
if (!dbConnected) {
|
|
146
|
+
console.error("Database connection failed. Exiting...");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
server.listen(PORT, () => {
|
|
150
|
+
console.log(`Server running on ${PORT}`);
|
|
151
|
+
});
|
|
152
|
+
})();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## API Reference
|
|
160
|
+
|
|
161
|
+
### Response Helpers
|
|
162
|
+
|
|
163
|
+
#### `successResponse(data, message?)`
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
successResponse<T>(data: T, message?: string): SuccessResponse<T>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
res
|
|
171
|
+
.status(200)
|
|
172
|
+
.json(successResponse({ id: 1, name: "Alice" }, "User fetched successfully"));
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"success": true,
|
|
178
|
+
"message": "User fetched successfully",
|
|
179
|
+
"data": { "id": 1, "name": "Alice" }
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
#### `errorResponse(message, errors?, stack?)`
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
errorResponse(message: string, errors?: any, stack?: string): ErrorResponse
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
You'll rarely call this directly — `errorMiddleware` calls it internally.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
res
|
|
195
|
+
.status(400)
|
|
196
|
+
.json(errorResponse("Validation failed", { field: "email", issue: "Required" }));
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"success": false,
|
|
202
|
+
"message": "Validation failed",
|
|
203
|
+
"errors": { "field": "email", "issue": "Required" }
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### `NexusError`
|
|
210
|
+
|
|
211
|
+
The single error class used throughout the library. Extends the native `Error`. Throw it inside a route (or anywhere downstream of Express's error handling) and let `errorMiddleware` handle the rest.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
new NexusError(message: string, statusCode?: number, errors?: any, isOperational?: boolean)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
| Parameter | Type | Default | Description |
|
|
218
|
+
| --------------- | --------- | ----------- | -------------------------------------------------- |
|
|
219
|
+
| `message` | `string` | — | Human-readable error message |
|
|
220
|
+
| `statusCode` | `number` | `500` | HTTP status code |
|
|
221
|
+
| `errors` | `any` | `undefined` | Additional error details or field-level errors |
|
|
222
|
+
| `isOperational` | `boolean` | `true` | Marks the error as an expected/handled error |
|
|
223
|
+
|
|
224
|
+
There are no built-in subclasses (no `BadRequestError`, `NotFoundError`, etc.) — pass the status code explicitly:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
throw new NexusError("Invalid request body", 400);
|
|
228
|
+
throw new NexusError("Invalid token", 401);
|
|
229
|
+
throw new NexusError("You do not have access to this resource", 403);
|
|
230
|
+
throw new NexusError("Post not found", 404);
|
|
231
|
+
throw new NexusError("Validation failed", 422, [
|
|
232
|
+
{ field: "email", message: "Must be a valid email address" },
|
|
233
|
+
{ field: "password", message: "Must be at least 8 characters" },
|
|
234
|
+
]);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### Error Middleware
|
|
240
|
+
|
|
241
|
+
#### `errorMiddleware`
|
|
242
|
+
|
|
243
|
+
A **tuple** of two Express handlers: `[routeNotFoundHandler, globalErrorHandler]`. Must be spread when registered, and registered **after all routes**.
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
import { errorMiddleware } from "nexus-backend";
|
|
247
|
+
|
|
248
|
+
app.use(...errorMiddleware);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Behaviour**
|
|
252
|
+
|
|
253
|
+
- `routeNotFoundHandler` — runs when no route matched the request; throws a `NexusError` with a `404` status for the unmatched URL.
|
|
254
|
+
- `globalErrorHandler` — catches any error passed via `next(err)` (or thrown in an async handler caught by Express 5 / your async wrapper). If the error is a `NexusError`, its own `statusCode`, `message`, and `errors` are used. Any other error falls back to `500 Internal Server Error`.
|
|
255
|
+
- When `NODE_ENV === "development"`, the response includes a `stack` field with the full stack trace. It's omitted otherwise.
|
|
256
|
+
|
|
257
|
+
**Development response example**
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
"success": false,
|
|
262
|
+
"message": "Post not found",
|
|
263
|
+
"errors": null,
|
|
264
|
+
"stack": "NexusError: Post not found\n at ..."
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
### MongoDB Connection
|
|
271
|
+
|
|
272
|
+
#### `connectMongoDb(config)`
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
connectMongoDb(config: MongoConfig): Promise<boolean>
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Connects to a sharded MongoDB Atlas cluster using a manually constructed connection string (rather than the `mongodb+srv://` DNS-based form). Logs progress and returns `true`/`false` instead of throwing, so you can decide how to handle a failed connection (e.g. exit the process).
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
interface MongoConfig {
|
|
282
|
+
subDomain: string;
|
|
283
|
+
userName: string;
|
|
284
|
+
password: string;
|
|
285
|
+
cluster: string;
|
|
286
|
+
dbName: string;
|
|
287
|
+
shards: [string, string, string]; // exactly 3 shard hostnames
|
|
288
|
+
replicaSet: string;
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { connectMongoDb, type MongoConfig } from "nexus-backend";
|
|
294
|
+
|
|
295
|
+
const config: MongoConfig = {
|
|
296
|
+
subDomain: "abc123",
|
|
297
|
+
userName: "dbUser",
|
|
298
|
+
password: "dbPass",
|
|
299
|
+
cluster: "MyCluster",
|
|
300
|
+
dbName: "myapp",
|
|
301
|
+
shards: ["shard-00-00", "shard-00-01", "shard-00-02"],
|
|
302
|
+
replicaSet: "atlas-xxxxx-shard-0",
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const connected = await connectMongoDb(config);
|
|
306
|
+
if (!connected) process.exit(1);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
### Caching
|
|
312
|
+
|
|
313
|
+
#### `Cache`
|
|
314
|
+
|
|
315
|
+
An in-memory cache built on top of [`node-cache`](https://www.npmjs.com/package/node-cache), adding namespacing and tag-based invalidation.
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { Cache } from "nexus-backend";
|
|
319
|
+
|
|
320
|
+
const cache = new Cache({ stdTTL: 120 }); // any node-cache options
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
| Method | Signature | Description |
|
|
324
|
+
|---|---|---|
|
|
325
|
+
| `setItem` | `(key, value, ttl?, namespace?, tags?) => void` | Stores a value (auto JSON-stringified if not a string) |
|
|
326
|
+
| `getItem` | `<T>(key, namespace?) => T \| undefined` | Retrieves and auto-parses a value |
|
|
327
|
+
| `getOrSetItem` | `<T>(key, computeFn, ttl?, namespace?, tags?) => Promise<T>` | Returns cached value, or computes + stores it if missing |
|
|
328
|
+
| `deleteItem` | `(key, namespace?) => number` | Deletes a single key |
|
|
329
|
+
| `clearNamespace` | `(namespace?) => void` | Clears all keys under a namespace, or everything if omitted |
|
|
330
|
+
| `clearTag` | `(tag) => void` | Deletes every key associated with a tag |
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
// cache-and-recompute pattern
|
|
334
|
+
const products = await cache.getOrSetItem(
|
|
335
|
+
"all-products",
|
|
336
|
+
() => fetchProductsFromDb(),
|
|
337
|
+
300, // ttl in seconds
|
|
338
|
+
"products", // namespace
|
|
339
|
+
["catalog"] // tags
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// invalidate everything tagged "catalog" after an update
|
|
343
|
+
cache.clearTag("catalog");
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### `cacheMemory`
|
|
347
|
+
|
|
348
|
+
A ready-to-use singleton `Cache` instance, for cases where you don't need a custom configuration:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { cacheMemory } from "nexus-backend";
|
|
352
|
+
|
|
353
|
+
cacheMemory.setItem("user:1", { id: 1, name: "Alice" }, 60);
|
|
354
|
+
const user = cacheMemory.getItem("user:1");
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
### Environment Variables
|
|
360
|
+
|
|
361
|
+
#### `requiredEnv(value, key)`
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
requiredEnv(value: string | undefined, key: string): string
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Throws if the environment variable is missing, otherwise returns it — useful for failing fast at startup instead of getting `undefined` deep in your app.
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import { requiredEnv } from "nexus-backend";
|
|
371
|
+
|
|
372
|
+
const port = requiredEnv(process.env.PORT, "PORT");
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
### Random Number Generator
|
|
378
|
+
|
|
379
|
+
#### `random(digit)`
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
random(digit: number): string
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Generates a cryptographically-random numeric string of a fixed length (e.g. for OTPs). Uses Node's `crypto.randomInt`, not `Math.random()`.
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import { random } from "nexus-backend";
|
|
389
|
+
|
|
390
|
+
const otp = random(6); // e.g. "483920"
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
### Mailer
|
|
396
|
+
|
|
397
|
+
#### `Mailer`
|
|
398
|
+
|
|
399
|
+
A thin wrapper around [Resend](https://resend.com/) for sending transactional emails.
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
new Mailer(config: MailerConfig)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
interface MailerConfig {
|
|
407
|
+
brandName: string;
|
|
408
|
+
smtpProvider: Resend; // a Resend client instance
|
|
409
|
+
fromEmail: string;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
interface EmailOptions {
|
|
413
|
+
to: string;
|
|
414
|
+
subject: string;
|
|
415
|
+
body: string; // sent as HTML
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
import { Mailer } from "nexus-backend";
|
|
421
|
+
import { Resend } from "resend";
|
|
422
|
+
|
|
423
|
+
const mailer = new Mailer({
|
|
424
|
+
brandName: "MyApp",
|
|
425
|
+
smtpProvider: new Resend(requiredEnv(process.env.RESEND_API_KEY, "RESEND_API_KEY")),
|
|
426
|
+
fromEmail: "noreply@myapp.com",
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const sent = await mailer.sendMail({
|
|
430
|
+
to: "user@example.com",
|
|
431
|
+
subject: "Welcome!",
|
|
432
|
+
body: "<p>Thanks for signing up.</p>",
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
if (!sent) {
|
|
436
|
+
// Resend API returned an error, or the request itself failed
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
`sendMail` never throws — it returns `false` on any failure (Resend API error or network error) so you can handle it without a `try/catch`.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
### File Uploads (Nexus Cloud)
|
|
445
|
+
|
|
446
|
+
#### `uploader`
|
|
447
|
+
|
|
448
|
+
A Telegram-Bot-backed file storage helper — files are sent to a Telegram chat and retrieved via Telegram's file API, giving you free file hosting without S3/Cloudinary.
|
|
449
|
+
|
|
450
|
+
> **Setup required:** `BOT_TOKEN` and `CHAT_ID` must be supplied via environment variables in your deployment — never hardcode them in source, since this package may be installed by others or its source inspected. Set `NEXUS_CLOUD_BOT_TOKEN` and `NEXUS_CLOUD_CHAT_ID` before using this feature.
|
|
451
|
+
|
|
452
|
+
| Property | Signature | Description |
|
|
453
|
+
|---|---|---|
|
|
454
|
+
| `upload` | `multer instance (memoryStorage, accepts any file type)` | Use as Express middleware to parse `multipart/form-data` |
|
|
455
|
+
| `uploadToNexusCloud` | `(file: Express.Multer.File \| undefined, label: string) => Promise<string \| undefined>` | Uploads a file, returns a Telegram `file_id` |
|
|
456
|
+
| `getNexusDownloadUrl` | `(fileId: string) => Promise<string>` | Resolves a `file_id` into a direct, time-limited download URL |
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
import { uploader } from "nexus-backend";
|
|
460
|
+
import express from "express";
|
|
461
|
+
|
|
462
|
+
const router = express.Router();
|
|
463
|
+
|
|
464
|
+
router.post(
|
|
465
|
+
"/upload",
|
|
466
|
+
uploader.upload.single("file"),
|
|
467
|
+
async (req, res) => {
|
|
468
|
+
const fileId = await uploader.uploadToNexusCloud(req.file, "profile-picture");
|
|
469
|
+
if (!fileId) {
|
|
470
|
+
return res.status(400).json({ success: false, message: "No file provided" });
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const downloadUrl = await uploader.getNexusDownloadUrl(fileId);
|
|
474
|
+
res.json({ success: true, data: { fileId, downloadUrl } });
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
> **Note:** Telegram file URLs are not permanent — `getFile` results expire after a time window, so call `getNexusDownloadUrl` fresh each time you need to serve the file rather than caching the URL long-term. Store the `file_id` (which doesn't expire) instead.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Response Shape
|
|
484
|
+
|
|
485
|
+
#### Success
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
interface SuccessResponse<T> {
|
|
489
|
+
success: true;
|
|
490
|
+
message?: string;
|
|
491
|
+
data: T;
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
#### Error
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
interface ErrorResponse {
|
|
499
|
+
success: false;
|
|
500
|
+
message: string;
|
|
501
|
+
errors?: any;
|
|
502
|
+
stack?: string; // only present in development
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## TypeScript
|
|
509
|
+
|
|
510
|
+
nexus-backend is written in TypeScript and ships with type declarations out of the box. No `@types` package is needed.
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
import type { SuccessResponse, ErrorResponse } from "nexus-backend";
|
|
514
|
+
|
|
515
|
+
type ApiResult<T> = SuccessResponse<T> | ErrorResponse;
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
---
|
|
519
|
+
|
|
520
|
+
## License
|
|
521
|
+
|
|
522
|
+
MIT
|