nexus-backend 1.1.2 → 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 CHANGED
@@ -1,349 +1,522 @@
1
- # Nexus Backend
2
-
3
- A lightweight backend utility library for [Express.js](https://expressjs.com/) providing structured error classes, standardised API response helpers, and a plug-and-play error handling middleware.
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
- - [Error Classes](#error-classes)
14
- - [Error Middleware](#error-middleware)
15
- - [Response Shape](#response-shape)
16
- - [TypeScript](#typescript)
17
- - [License](#license)
18
-
19
- ---
20
-
21
- ## Installation
22
-
23
- ```bash
24
-
25
- npm install express mongoose cors helmet morgan cookie-parser
26
- ```
27
-
28
- Express, mongoose, cors, helmet, morgan, and cookie-parser are peer dependencies — make sure they are installed in your project:
29
-
30
- ```bash
31
- npm install nexus-backend
32
- ```
33
-
34
- ---
35
-
36
- ## Quick Start
37
-
38
- ```ts
39
- // src/config/mongoConfig.ts
40
- import { requiredEnv, type MongoConfig } from "nexus-backend";
41
-
42
- const dbConfig: MongoConfig = {
43
- subDomain: requiredEnv(process.env.DB_HOST, "DB_HOST"),
44
- userName: requiredEnv(process.env.DB_USERNAME, "DB_USERNAME"),
45
- password: requiredEnv(process.env.DB_PASSWORD, "DB_PASSWORD"),
46
- cluster: requiredEnv(process.env.DB_CLUSTER, "DB_CLUSTER"),
47
- dbName: requiredEnv(process.env.DB_NAME, "DB_NAME"),
48
- };
49
- export default dbConfig;
50
- ```
51
-
52
- ```ts
53
- // src/server.ts
54
- import express, { type Application, Request, Response } from "express";
55
- import http from "http";
56
- import {
57
- successResponse,
58
- errorMiddleware,
59
- NotFoundError,
60
- ValidationError,
61
- } from "nexus-backend";
62
- import cors from "cors";
63
- import helmet from "helmet";
64
- import morgan from "morgan";
65
- import cookieParser from "cookie-parser";
66
- import { Server } from "socket.io";
67
-
68
- const app: Application = express();
69
-
70
- if (process.env.NODE_ENV !== "production") {
71
- app.use(morgan("dev"));
72
- }
73
- app.set("trust proxy", 1);
74
- app.use(helmet());
75
- app.use(
76
- cors({
77
- origin: process.env.FRONTEND_URL,
78
- credentials: true,
79
- }),
80
- );
81
-
82
- app.use(express.json({ limit: "500mb" })); // Adjust the limit as needed
83
- app.use(express.urlencoded({ extended: true, limit: "500mb" })); // For parsing application/x-www-form-urlencoded
84
- app.use(cookieParser());
85
-
86
- // Example route
87
- app.get("/users/:id", async (req: Request, res: Response) => {
88
- const user = await getUserById(req.params.id);
89
-
90
- if (!user) {
91
- throw new NotFoundError("User not found");
92
- }
93
-
94
- res.json(successResponse(user, "User fetched successfully"));
95
- });
96
-
97
- // Register error middleware last
98
- app.use(errorMiddleware);
99
- const server = http.createServer(app);
100
- export const io = new Server(server, {
101
- cors: {
102
- origin: process.env.FRONTEND_URL,
103
- credentials: true,
104
- },
105
- });
106
-
107
- export default server;
108
- ```
109
-
110
- ```ts
111
- // src/index.ts
112
- import "dotenv/config";
113
- import { connectMongoDb } from "nexus-backend";
114
- import dns from "dns";
115
- import dbConfig from "./config/mongoConfig";
116
- import server from "./server";
117
- const PORT = process.env.PORT || 5000;
118
- async () => {
119
- dns.setServers(["1.1.1.1", "1.0.0.1"]);
120
- const dbConnected = await connectMongoDb(dbConfig);
121
- if (!dbConnected) {
122
- console.error("Database connection failed. Exiting...");
123
- process.exit(1);
124
- }
125
- server.listen(PORT, () => {
126
- console.log(`Server running on ${PORT}`);
127
- });
128
- };
129
- ```
130
-
131
- ## API Reference
132
-
133
- ### Response Helpers
134
-
135
- #### `successResponse(data, message?)`
136
-
137
- Returns a structured success payload. Pass the result directly to `res.json()`.
138
-
139
- ```ts
140
- successResponse<T>(data: T, message?: string): SuccessResponse<T>
141
- ```
142
-
143
- **Example**
144
-
145
- ```ts
146
- res
147
- .status(200)
148
- .json(successResponse({ id: 1, name: "Alice" }, "User fetched successfully"));
149
- ```
150
-
151
- ```json
152
- {
153
- "success": true,
154
- "message": "User fetched successfully",
155
- "data": { "id": 1, "name": "Alice" }
156
- }
157
- ```
158
-
159
- ---
160
-
161
- #### `errorResponse(message, errors?, stack?)`
162
-
163
- Returns a structured error payload. You will rarely call this directly — `errorMiddleware` calls it internally. Useful if you need to construct an error response manually.
164
-
165
- ```ts
166
- errorResponse(message: string, errors?: any, stack?: string): ErrorResponse
167
- ```
168
-
169
- **Example**
170
-
171
- ```ts
172
- res
173
- .status(400)
174
- .json(
175
- errorResponse("Validation failed", { field: "email", issue: "Required" }),
176
- );
177
- ```
178
-
179
- ```json
180
- {
181
- "success": false,
182
- "message": "Validation failed",
183
- "errors": { "field": "email", "issue": "Required" }
184
- }
185
- ```
186
-
187
- ---
188
-
189
- ### Error Classes
190
-
191
- All error classes extend the base `ApiError` class, which itself extends the native `Error`. Throw any of these inside a route and let `errorMiddleware` handle the rest.
192
-
193
- #### `ApiError`
194
-
195
- The base error class. Use this when none of the specific subclasses fit your use case.
196
-
197
- ```ts
198
- new ApiError(message: string, statusCode?: number, errors?: any, isOperational?: boolean)
199
- ```
200
-
201
- | Parameter | Type | Default | Description |
202
- | --------------- | --------- | ----------- | ------------------------------------------------ |
203
- | `message` | `string` | — | Human-readable error message |
204
- | `statusCode` | `number` | `500` | HTTP status code |
205
- | `errors` | `any` | `undefined` | Additional error details or field errors |
206
- | `isOperational` | `boolean` | `true` | Marks the error as an expected operational error |
207
-
208
- ---
209
-
210
- #### `BadRequestError`
211
-
212
- **Status:** `400 Bad Request`
213
-
214
- Use when the client sends a malformed or invalid request.
215
-
216
- ```ts
217
- throw new BadRequestError("Invalid request body");
218
- ```
219
-
220
- ---
221
-
222
- #### `UnauthorizedError`
223
-
224
- **Status:** `401 Unauthorized`
225
-
226
- Use when authentication is missing or invalid.
227
-
228
- ```ts
229
- throw new UnauthorizedError("Invalid token");
230
- ```
231
-
232
- ---
233
-
234
- #### `ForbiddenError`
235
-
236
- **Status:** `403 Forbidden`
237
-
238
- Use when an authenticated user lacks permission to access a resource.
239
-
240
- ```ts
241
- throw new ForbiddenError("You do not have access to this resource");
242
- ```
243
-
244
- ---
245
-
246
- #### `NotFoundError`
247
-
248
- **Status:** `404 Not Found`
249
-
250
- Use when a requested resource does not exist.
251
-
252
- ```ts
253
- throw new NotFoundError("Post not found");
254
- ```
255
-
256
- ---
257
-
258
- #### `ValidationError`
259
-
260
- **Status:** `422 Unprocessable Entity`
261
-
262
- Use when request data fails validation. Pass field-level errors via the `errors` argument.
263
-
264
- ```ts
265
- throw new ValidationError("Validation failed", [
266
- { field: "email", message: "Must be a valid email address" },
267
- { field: "password", message: "Must be at least 8 characters" },
268
- ]);
269
- ```
270
-
271
- ---
272
-
273
- ### Error Middleware
274
-
275
- #### `errorMiddleware`
276
-
277
- An Express-compatible error handling middleware. It catches any error passed to `next(err)`, determines the appropriate HTTP status code and message, and sends a structured `ErrorResponse`.
278
-
279
- Register it **after all routes** and other middleware.
280
-
281
- ```ts
282
- import { errorMiddleware } from "nexusjs";
283
-
284
- app.use(errorMiddleware);
285
- ```
286
-
287
- **Behaviour**
288
-
289
- - If the error is an instance of `ApiError` (or any subclass), the middleware uses the error's own `statusCode` and `message`.
290
- - For all other unhandled errors, it falls back to `500 Internal Server Error`.
291
- - When `NODE_ENV` is set to `"development"`, the response includes a `stack` field with the full stack trace to aid debugging. The `stack` field is omitted in production.
292
-
293
- **Development response example**
294
-
295
- ```json
296
- {
297
- "success": false,
298
- "message": "User not found",
299
- "errors": null,
300
- "stack": "NotFoundError: User not found\n at ..."
301
- }
302
- ```
303
-
304
- ---
305
-
306
- ## Response Shape
307
-
308
- All responses follow a consistent structure.
309
-
310
- #### Success
311
-
312
- ```ts
313
- interface SuccessResponse<T> {
314
- success: true;
315
- message?: string;
316
- data: T;
317
- }
318
- ```
319
-
320
- #### Error
321
-
322
- ```ts
323
- interface ErrorResponse {
324
- success: false;
325
- message: string;
326
- errors?: any;
327
- stack?: string; // only present in development
328
- }
329
- ```
330
-
331
- ---
332
-
333
- ## TypeScript
334
-
335
- nexusjs is written in TypeScript and ships with type declarations out of the box. No `@types` package is needed.
336
-
337
- `SuccessResponse` and `ErrorResponse` are exported and can be used to type your own response wrappers or API clients:
338
-
339
- ```ts
340
- import type { SuccessResponse, ErrorResponse } from "nexusjs";
341
-
342
- type ApiResult<T> = SuccessResponse<T> | ErrorResponse;
343
- ```
344
-
345
- ---
346
-
347
- ## License
348
-
349
- MIT
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