express-zod-api 21.0.0-beta.4 → 21.0.0-beta.6

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/CHANGELOG.md CHANGED
@@ -21,8 +21,8 @@
21
21
  - The `serializer` property of `Documentation` and `Integration` constructor argument removed;
22
22
  - The `originalError` property of `InputValidationError` and `OutputValidationError` removed (use `cause` instead);
23
23
  - The `getStatusCodeFromError()` method removed (use the `ensureHttpError().statusCode` instead);
24
- - The `Endpoint::getMethods()` method may now return `undefined`;
25
24
  - The `testEndpoint()` method can no longer test CORS headers — that function moved to `Routing` traverse;
25
+ - For `Endpoint`: `getMethods()` may return `undefined`, `getMimeTypes()` removed, `getSchema()` variants reduced;
26
26
  - Public properties `pairs`, `firstEndpoint` and `siblingMethods` of `DependsOnMethod` replaced with `entries`.
27
27
  - Consider the automated migration using the built-in ESLint rule.
28
28
 
@@ -56,6 +56,41 @@ const { servers } = await createServer(config, {});
56
56
 
57
57
  ## Version 20
58
58
 
59
+ ### v20.22.1
60
+
61
+ - Avoids startup logo distortion when the terminal is too narrow;
62
+ - Self-diagnosis for potential problems disabled in production mode to ensure faster startup:
63
+ - Warning about potentially unserializable schema for JSON operating endpoints was introduced in v20.15.0.
64
+
65
+ ### v20.22.0
66
+
67
+ - Featuring a helper to describe nested Routing for already assigned routes:
68
+ - Suppose you want to describe `Routing` for both `/v1/path` and `/v1/path/subpath` routes having Endpoints attached;
69
+ - Previously, an empty path segment was proposed for that purpose, but there is more elegant and readable way now;
70
+ - The `.nest()` method is available both on `Endpoint` and `DependsOnMethod` instances:
71
+
72
+ ```ts
73
+ import { Routing } from "express-zod-api";
74
+
75
+ // Describing routes /v1/path and /v1/path/subpath both having endpoints assigned:
76
+ const before: Routing = {
77
+ v1: {
78
+ path: {
79
+ "": endpointA,
80
+ subpath: endpointB,
81
+ },
82
+ },
83
+ };
84
+
85
+ const after: Routing = {
86
+ v1: {
87
+ path: endpointA.nest({
88
+ subpath: endpointB,
89
+ }),
90
+ },
91
+ };
92
+ ```
93
+
59
94
  ### v20.21.2
60
95
 
61
96
  - Fixed the example implementation in the generated client for endpoints using path params:
package/README.md CHANGED
@@ -32,17 +32,18 @@ Start your API server with I/O schema validation and custom middlewares in minut
32
32
  13. [Enabling compression](#enabling-compression)
33
33
  5. [Advanced features](#advanced-features)
34
34
  1. [Customizing input sources](#customizing-input-sources)
35
- 2. [Route path params](#route-path-params)
36
- 3. [Multiple schemas for one route](#multiple-schemas-for-one-route)
37
- 4. [Response customization](#response-customization)
38
- 5. [Error handling](#error-handling)
39
- 6. [Production mode](#production-mode)
40
- 7. [Non-object response](#non-object-response) including file downloads
41
- 8. [File uploads](#file-uploads)
42
- 9. [Serving static files](#serving-static-files)
43
- 10. [Connect to your own express app](#connect-to-your-own-express-app)
44
- 11. [Testing endpoints](#testing-endpoints)
45
- 12. [Testing middlewares](#testing-middlewares)
35
+ 2. [Nested routes](#nested-routes)
36
+ 3. [Route path params](#route-path-params)
37
+ 4. [Multiple schemas for one route](#multiple-schemas-for-one-route)
38
+ 5. [Response customization](#response-customization)
39
+ 6. [Error handling](#error-handling)
40
+ 7. [Production mode](#production-mode)
41
+ 8. [Non-object response](#non-object-response) including file downloads
42
+ 9. [File uploads](#file-uploads)
43
+ 10. [Serving static files](#serving-static-files)
44
+ 11. [Connect to your own express app](#connect-to-your-own-express-app)
45
+ 12. [Testing endpoints](#testing-endpoints)
46
+ 13. [Testing middlewares](#testing-middlewares)
46
47
  6. [Special needs](#special-needs)
47
48
  1. [Different responses for different status codes](#different-responses-for-different-status-codes)
48
49
  2. [Array response](#array-response) for migrating legacy APIs
@@ -287,12 +288,9 @@ const authMiddleware = new Middleware({
287
288
  handler: async ({ input: { key }, request, logger }) => {
288
289
  logger.debug("Checking the key and token");
289
290
  const user = await db.Users.findOne({ key });
290
- if (!user) {
291
- throw createHttpError(401, "Invalid key");
292
- }
293
- if (request.headers.token !== user.token) {
291
+ if (!user) throw createHttpError(401, "Invalid key");
292
+ if (request.headers.token !== user.token)
294
293
  throw createHttpError(401, "Invalid token");
295
- }
296
294
  return { user }; // provides endpoints with options.user
297
295
  },
298
296
  });
@@ -304,10 +302,9 @@ By using `.addMiddleware()` method before `.build()` you can connect it to the e
304
302
  const yourEndpoint = defaultEndpointsFactory
305
303
  .addMiddleware(authMiddleware)
306
304
  .build({
307
- // ...,
308
- handler: async ({ options }) => {
309
- // options.user is the user returned by authMiddleware
310
- },
305
+ handler: async ({ options: { user } }) => {
306
+ // user is the one returned by authMiddleware
307
+ }, // ...
311
308
  });
312
309
  ```
313
310
 
@@ -321,7 +318,7 @@ import { defaultEndpointsFactory } from "express-zod-api";
321
318
  const factory = defaultEndpointsFactory
322
319
  .addMiddleware(authMiddleware) // add Middleware instance or use shorter syntax:
323
320
  .addMiddleware({
324
- handler: async ({ options: { user } }) => ({}), // options.user from authMiddleware
321
+ handler: async ({ options: { user } }) => ({}), // user from authMiddleware
325
322
  });
326
323
  ```
327
324
 
@@ -535,18 +532,14 @@ const updateUserEndpoint = defaultEndpointsFactory.build({
535
532
  method: "post",
536
533
  input: z.object({
537
534
  userId: z.string(),
538
- birthday: ez.dateIn(), // string -> Date
535
+ birthday: ez.dateIn(), // string -> Date in handler
539
536
  }),
540
537
  output: z.object({
541
- createdAt: ez.dateOut(), // Date -> string
538
+ createdAt: ez.dateOut(), // Date -> string in response
539
+ }),
540
+ handler: async ({ input }) => ({
541
+ createdAt: new Date("2022-01-22"), // 2022-01-22T00:00:00.000Z
542
542
  }),
543
- handler: async ({ input }) => {
544
- // input.birthday is Date
545
- return {
546
- // transmitted as "2022-01-22T00:00:00.000Z"
547
- createdAt: new Date("2022-01-22"),
548
- };
549
- },
550
543
  });
551
544
  ```
552
545
 
@@ -564,7 +557,6 @@ That function has several parameters and can be asynchronous.
564
557
  import { createConfig } from "express-zod-api";
565
558
 
566
559
  const config = createConfig({
567
- // ... other options
568
560
  cors: ({ defaultHeaders, request, endpoint, logger }) => ({
569
561
  ...defaultHeaders,
570
562
  "Access-Control-Max-Age": "5000",
@@ -590,8 +582,7 @@ const config = createConfig({
590
582
  key: fs.readFileSync("privkey.pem", "utf-8"),
591
583
  },
592
584
  listen: 443, // port, UNIX socket or options
593
- },
594
- // ... cors, logger, etc
585
+ }, // ... cors, logger, etc
595
586
  });
596
587
 
597
588
  // 'await' is only needed if you're going to use the returned entities.
@@ -718,14 +709,13 @@ In order to receive a compressed response the client should include the followin
718
709
 
719
710
  You can customize the list of `request` properties that are combined into `input` that is being validated and available
720
711
  to your endpoints and middlewares. The order here matters: each next item in the array has a higher priority than its
721
- previous sibling.
712
+ previous sibling. The following arrangement is default:
722
713
 
723
714
  ```typescript
724
715
  import { createConfig } from "express-zod-api";
725
716
 
726
717
  createConfig({
727
718
  inputSources: {
728
- // the defaults are:
729
719
  get: ["query", "params"],
730
720
  post: ["body", "params", "files"],
731
721
  put: ["body", "params"],
@@ -735,21 +725,32 @@ createConfig({
735
725
  });
736
726
  ```
737
727
 
728
+ ## Nested routes
729
+
730
+ Suppose you want to assign both `/v1/path` and `/v1/path/subpath` routes with Endpoints:
731
+
732
+ ```typescript
733
+ import { Routing } from "express-zod-api";
734
+
735
+ const routing: Routing = {
736
+ v1: {
737
+ path: endpointA.nest({
738
+ subpath: endpointB,
739
+ }),
740
+ },
741
+ };
742
+ ```
743
+
738
744
  ## Route path params
739
745
 
740
- You can describe the route of the endpoint using parameters:
746
+ You can assign your Endpoint to a route like `/v1/user/:id` where `:id` is the path parameter:
741
747
 
742
748
  ```typescript
743
749
  import { Routing } from "express-zod-api";
744
750
 
745
751
  const routing: Routing = {
746
752
  v1: {
747
- user: {
748
- // route path /v1/user/:id, where :id is the path param
749
- ":id": getUserEndpoint,
750
- // use the empty string to represent /v1/user if needed:
751
- // "": listAllUsersEndpoint,
752
- },
753
+ user: { ":id": getUserEndpoint },
753
754
  },
754
755
  };
755
756
  ```
@@ -764,12 +765,8 @@ const getUserEndpoint = endpointsFactory.build({
764
765
  // other inputs (in query):
765
766
  withExtendedInformation: z.boolean().optional(),
766
767
  }),
767
- output: z.object({
768
- /* ... */
769
- }),
770
- handler: async ({ input: { id } }) => {
771
- // id is the route path param, number
772
- },
768
+ output: z.object({}),
769
+ handler: async ({ input: { id } }) => ({}), // id is number,
773
770
  });
774
771
  ```
775
772
 
@@ -873,6 +870,7 @@ origins of errors that could happen in runtime and be handled the following way:
873
870
  Consider enabling production mode by setting `NODE_ENV` environment variable to `production` for your deployment:
874
871
 
875
872
  - Express activates some [performance optimizations](https://expressjs.com/en/advanced/best-practice-performance.html);
873
+ - Self-diagnosis for potential problems is disabled to ensure faster startup;
876
874
  - The `defaultResultHandler`, `defaultEndpointsFactory` and `LastResortHandler` generalize server-side error messages
877
875
  in negative responses in order to improve the security of your API by not disclosing the exact causes of errors:
878
876
  - Throwing errors that have or imply `5XX` status codes become just `Internal Server Error` message in response;
@@ -905,17 +903,12 @@ const fileStreamingEndpointsFactory = new EndpointsFactory(
905
903
  positive: { schema: ez.file("buffer"), mimeType: "image/*" },
906
904
  negative: { schema: z.string(), mimeType: "text/plain" },
907
905
  handler: ({ response, error, output }) => {
908
- if (error) {
909
- response.status(400).send(error.message);
910
- return;
911
- }
912
- if ("filename" in output) {
906
+ if (error) return void response.status(400).send(error.message);
907
+ if ("filename" in output)
913
908
  fs.createReadStream(output.filename).pipe(
914
909
  response.type(output.filename),
915
910
  );
916
- } else {
917
- response.status(400).send("Filename is missing");
918
- }
911
+ else response.status(400).send("Filename is missing");
919
912
  },
920
913
  }),
921
914
  );
@@ -949,9 +942,7 @@ const config = createConfig({
949
942
  limits: { fileSize: 51200 }, // 50 KB
950
943
  limitError: createHttpError(413, "The file is too large"), // handled by errorHandler in config
951
944
  beforeUpload: ({ request, logger }) => {
952
- if (!canUpload(request)) {
953
- throw createHttpError(403, "Not authorized");
954
- }
945
+ if (!canUpload(request)) throw createHttpError(403, "Not authorized");
955
946
  },
956
947
  },
957
948
  });
@@ -1284,9 +1275,7 @@ const exampleEndpoint = defaultEndpointsFactory.build({
1284
1275
  .object({
1285
1276
  id: z.number().describe("the ID of the user"),
1286
1277
  })
1287
- .example({
1288
- id: 123,
1289
- }),
1278
+ .example({ id: 123 }),
1290
1279
  // ..., similarly for output and middlewares
1291
1280
  });
1292
1281
  ```
@@ -1309,9 +1298,8 @@ import {
1309
1298
  } from "express-zod-api";
1310
1299
 
1311
1300
  const config = createConfig({
1312
- // ..., use the simple or the advanced syntax:
1313
1301
  tags: {
1314
- users: "Everything about the users",
1302
+ users: "Everything about the users", // or advanced syntax:
1315
1303
  files: {
1316
1304
  description: "Everything about the files processing",
1317
1305
  url: "https://example.com",
@@ -1326,7 +1314,6 @@ const taggedEndpointsFactory = new EndpointsFactory({
1326
1314
  });
1327
1315
 
1328
1316
  const exampleEndpoint = taggedEndpointsFactory.build({
1329
- // ...
1330
1317
  tag: "users", // or array ["users", "files"]
1331
1318
  });
1332
1319
  ```
@@ -1365,12 +1352,10 @@ const ruleForClient: Producer = (
1365
1352
  ) => ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
1366
1353
 
1367
1354
  new Documentation({
1368
- /* config, routing, title, version */
1369
1355
  brandHandling: { [myBrand]: ruleForDocs },
1370
1356
  });
1371
1357
 
1372
1358
  new Integration({
1373
- /* routing */
1374
1359
  brandHandling: { [myBrand]: ruleForClient },
1375
1360
  });
1376
1361
  ```