@nano-app/backend 0.10.0 → 0.11.0
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 +32 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
Preconfigured Express server with **file-convention routes** and **plugins**: it mounts the base middlewares, discovers your routes, formats every error as JSON, and groups extra endpoints in plugins.
|
|
6
6
|
|
|
7
|
-
- Node ≥
|
|
7
|
+
- Node ≥ 22, ESM.
|
|
8
8
|
- Optional PostgreSQL layer through your generated Prisma client. Disabled by default: without it no database code is loaded.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
|
|
11
|
+
pnpm add @nano-app/backend
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Quick start
|
|
@@ -24,17 +24,17 @@ src/
|
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
26
|
// src/app.ts
|
|
27
|
-
import { createNanoApp } from
|
|
27
|
+
import { createNanoApp } from '@nano-app/backend';
|
|
28
28
|
|
|
29
29
|
createNanoApp({ port: 3000 });
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
```ts
|
|
33
33
|
// src/routes/health.route.ts
|
|
34
|
-
import { createRoute } from
|
|
34
|
+
import { createRoute } from '@nano-app/backend';
|
|
35
35
|
|
|
36
36
|
export default createRoute({
|
|
37
|
-
GET: async () => ({ success: true, result: { status:
|
|
37
|
+
GET: async () => ({ success: true, result: { status: 'ok' } }),
|
|
38
38
|
});
|
|
39
39
|
```
|
|
40
40
|
|
|
@@ -52,9 +52,9 @@ Each `*.route.ts` under `mainPlugin.routesDir` default-exports an object with th
|
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
54
|
export default createRoute({
|
|
55
|
-
expression:
|
|
55
|
+
expression: '/:id', // → /users/:id
|
|
56
56
|
GET: async req => ({ success: true, result: { id: req.params.id } }),
|
|
57
|
-
DELETE: async req => ({ success: true, message:
|
|
57
|
+
DELETE: async req => ({ success: true, message: 'Deleted' }),
|
|
58
58
|
});
|
|
59
59
|
```
|
|
60
60
|
|
|
@@ -89,9 +89,9 @@ Every option is optional.
|
|
|
89
89
|
|
|
90
90
|
```ts
|
|
91
91
|
createNanoApp({
|
|
92
|
-
settings: {
|
|
92
|
+
settings: { 'trust proxy': 1 },
|
|
93
93
|
packages: {
|
|
94
|
-
cors: { origin:
|
|
94
|
+
cors: { origin: 'https://my-app.com' },
|
|
95
95
|
helmet: false,
|
|
96
96
|
rateLimit: { windowMs: 60_000, max: 120 },
|
|
97
97
|
},
|
|
@@ -103,7 +103,7 @@ createNanoApp({
|
|
|
103
103
|
Off until enabled: at startup nano-app generates the Prisma client of your schema, checks that its tables can be read and passes it to your handlers as their second parameter. **It never modifies your database**: applying the schema (`prisma db push`, `prisma migrate dev`, `prisma migrate deploy`) is up to you, with Prisma.
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
|
-
|
|
106
|
+
pnpm add prisma @prisma/client @prisma/adapter-pg pg # prisma too: the startup generates the client
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
```prisma
|
|
@@ -130,8 +130,8 @@ createNanoApp({ mainPlugin: { database: { enabled: true } } });
|
|
|
130
130
|
|
|
131
131
|
```ts
|
|
132
132
|
// src/routes/users.route.ts
|
|
133
|
-
import { createRoute } from
|
|
134
|
-
import type { PrismaClient } from
|
|
133
|
+
import { createRoute } from '@nano-app/backend';
|
|
134
|
+
import type { PrismaClient } from '../../.nano-app/prisma-clients/app/generated/client';
|
|
135
135
|
|
|
136
136
|
export default createRoute<PrismaClient>({
|
|
137
137
|
GET: async (_req, { db }) => ({ success: true, result: await db!.user.findMany() }),
|
|
@@ -157,12 +157,12 @@ export default createRoute<PrismaClient>({
|
|
|
157
157
|
Users, sign-in and sessions with [Better Auth](https://www.better-auth.com), on your database. Off until `packages.betterAuth` gets its options (everything but `database`: nano-app passes your Prisma client).
|
|
158
158
|
|
|
159
159
|
```bash
|
|
160
|
-
|
|
160
|
+
pnpm add better-auth
|
|
161
161
|
```
|
|
162
162
|
|
|
163
163
|
```ts
|
|
164
164
|
// src/auth.ts: the options, and an instance only for the Better Auth CLI
|
|
165
|
-
import { betterAuth, type BetterAuthOptions } from
|
|
165
|
+
import { betterAuth, type BetterAuthOptions } from 'better-auth';
|
|
166
166
|
|
|
167
167
|
export const authOptions = { emailAndPassword: { enabled: true } } satisfies BetterAuthOptions;
|
|
168
168
|
export const auth = betterAuth(authOptions);
|
|
@@ -170,8 +170,8 @@ export const auth = betterAuth(authOptions);
|
|
|
170
170
|
|
|
171
171
|
```ts
|
|
172
172
|
// src/app.ts
|
|
173
|
-
import { createNanoApp } from
|
|
174
|
-
import { authOptions } from
|
|
173
|
+
import { createNanoApp } from '@nano-app/backend';
|
|
174
|
+
import { authOptions } from './auth';
|
|
175
175
|
|
|
176
176
|
createNanoApp({
|
|
177
177
|
packages: { betterAuth: authOptions },
|
|
@@ -203,20 +203,20 @@ A plugin is a group of endpoints mounted under a base path. Your routes are moun
|
|
|
203
203
|
|
|
204
204
|
```ts
|
|
205
205
|
// src/plugins/hello/index.ts
|
|
206
|
-
import { createPlugin } from
|
|
207
|
-
import { dirname, resolve } from
|
|
208
|
-
import { fileURLToPath } from
|
|
206
|
+
import { createPlugin } from '@nano-app/backend';
|
|
207
|
+
import { dirname, resolve } from 'node:path';
|
|
208
|
+
import { fileURLToPath } from 'node:url';
|
|
209
209
|
|
|
210
210
|
export default createPlugin((options?: { basePath?: string }) => ({
|
|
211
|
-
name:
|
|
212
|
-
basePath: options?.basePath ??
|
|
213
|
-
routesDir: resolve(dirname(fileURLToPath(import.meta.url)),
|
|
211
|
+
name: 'hello',
|
|
212
|
+
basePath: options?.basePath ?? '/hello',
|
|
213
|
+
routesDir: resolve(dirname(fileURLToPath(import.meta.url)), 'routes'),
|
|
214
214
|
}));
|
|
215
215
|
```
|
|
216
216
|
|
|
217
217
|
```ts
|
|
218
218
|
// src/plugins/hello/routes/index.route.ts → /hello
|
|
219
|
-
import { createRoute } from
|
|
219
|
+
import { createRoute } from '@nano-app/backend';
|
|
220
220
|
|
|
221
221
|
export default createRoute({
|
|
222
222
|
GET: async () => ({ success: true, result: { ok: true } }),
|
|
@@ -225,8 +225,8 @@ export default createRoute({
|
|
|
225
225
|
|
|
226
226
|
```ts
|
|
227
227
|
// src/app.ts
|
|
228
|
-
import { createNanoApp } from
|
|
229
|
-
import hello from
|
|
228
|
+
import { createNanoApp } from '@nano-app/backend';
|
|
229
|
+
import hello from './plugins/hello';
|
|
230
230
|
|
|
231
231
|
createNanoApp({ plugins: [hello()] });
|
|
232
232
|
```
|
|
@@ -245,12 +245,12 @@ Two levels. Only the plugin's receives the Express app, so nothing app-wide depe
|
|
|
245
245
|
|
|
246
246
|
```ts
|
|
247
247
|
export default createPlugin((options?: { publicPaths?: string[] }) => ({
|
|
248
|
-
name:
|
|
249
|
-
routesDir: resolve(dirname(fileURLToPath(import.meta.url)),
|
|
248
|
+
name: 'guard',
|
|
249
|
+
routesDir: resolve(dirname(fileURLToPath(import.meta.url)), 'routes'),
|
|
250
250
|
options: { publicPaths: options?.publicPaths ?? [] },
|
|
251
251
|
callbacks: {
|
|
252
252
|
onMount: async (app, { publicPaths }) => {
|
|
253
|
-
app.use((req, _res, next) => next(publicPaths.includes(req.path) ? undefined : { status: 401, message:
|
|
253
|
+
app.use((req, _res, next) => next(publicPaths.includes(req.path) ? undefined : { status: 401, message: 'Unauthorized' }));
|
|
254
254
|
},
|
|
255
255
|
},
|
|
256
256
|
}));
|
|
@@ -267,9 +267,9 @@ A plugin with a database follows the same rules, with its client in `.nano-app/p
|
|
|
267
267
|
The framework mounts the 404 and the error handler: an unmatched request becomes a `404`, and anything a handler throws becomes an `ApiResponse`.
|
|
268
268
|
|
|
269
269
|
```ts
|
|
270
|
-
throw { status: 409, message:
|
|
271
|
-
throw
|
|
272
|
-
throw new Error(
|
|
270
|
+
throw { status: 409, message: 'Already exists' }; // → 409 { success: false, warning: false, message: "Already exists" }
|
|
271
|
+
throw 'Field name is required'; // → 400 { success: false, message: "Field name is required" }
|
|
272
|
+
throw new Error('boom'); // → 500, logged to stderr
|
|
273
273
|
GET /unknown // → 404 { success: false, warning: false, message: "/unknown not found" }
|
|
274
274
|
```
|
|
275
275
|
|
|
@@ -282,7 +282,7 @@ GET /unknown // → 404 { success: false,
|
|
|
282
282
|
| `createPlugin(creator)` | Types a plugin factory and its options. |
|
|
283
283
|
| `createPluginCallbacks(callbacks)` | Types the callbacks of a plugin (`createPluginCallbacks<PrismaClient>` types `db`), e.g. to keep them in their own file. |
|
|
284
284
|
|
|
285
|
-
Also exported: the configuration types (`NanoAppConfig`, `PackagesConfig`, `PluginConfig`, `PluginStage`, `MainPluginConfig`, `DatabaseConfig`), the Express `Request` type and the HTTP contract (`ApiResponse`, `ApiPromise`, `QueryParams`, `RouteEntry`, `Handler`, `Handlers`, `NanoObject`) and the callback types (`Params`, `Callbacks`, `PluginCallbacks`).
|
|
285
|
+
Also exported: the `NanoApp` class that `createNanoApp` returns, the configuration types (`NanoAppConfig`, `PackagesConfig`, `PluginConfig`, `PluginStage`, `MainPluginConfig`, `DatabaseConfig`), the Express `Request` type and the HTTP contract (`ApiResponse`, `ApiPromise`, `QueryParams`, `RouteEntry`, `Handler`, `Handlers`, `NanoObject`) and the callback types (`Params`, `Callbacks`, `PluginCallbacks`).
|
|
286
286
|
|
|
287
287
|
## Development
|
|
288
288
|
|