Skip to content

Commit

Permalink
chore: v6.2.x (#21)
Browse files Browse the repository at this point in the history
* fix: narrow response types

makes response types not arbitrary. Users can no longer pass whatever status with whatever bodies

* feat(openapi): support zod openapi extension

* docs(openapi): improve docs

* feat(createapi): add support for nested routes

Allows specifying one level of nesting when defining the routing object

* docs(readme): nested routing example

Add an example of how to use nested/prefixed routes

* fix(createendpoint): require at least one handler

No longer allow an empty array to be passed to the handler parameter

* test(integration): add integration tests for documentation and input validation
  • Loading branch information
khaosdoctor authored Mar 30, 2024
1 parent 5139b65 commit 8d0b9d5
Show file tree
Hide file tree
Showing 12 changed files with 833 additions and 40 deletions.
142 changes: 136 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
# Expresso Framework
# Expresso Router

> Self documented, self validated, typescript-first API framework written on top of Express
> Self documented, self validated, typescript-first router for express
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->

<!-- code_chunk_output -->

- [Expresso Router](#expresso-router)
- [Features](#features)
- [Usage](#usage)
- [Defining an endpoint](#defining-an-endpoint)
- [Zod extension](#zod-extension)
- [Defining routes](#defining-routes)
- [Simple routes](#simple-routes)
- [Nested (prefixed) routes](#nested-prefixed-routes)
- [Putting everything together](#putting-everything-together)

<!-- /code_chunk_output -->

## Features

- Automatic input validation with [Zod](https://www.npmjs.com/package/zod)
- Automatic OpenAPI extension with Zod
- Type safe input and output
- Auto generated documentation

Expand All @@ -16,7 +33,7 @@ Expresso router's main function is the `createEndpoint` function. This function

When creating an endpoint, you need to describe its input, output and give it at least one handler.

The `input` property is an object containing a `body`, `params` and / or `query` properties, each being a Zod schema. The corresponding `req` properties will be validated and transformed using these schemas. You can also specify a `headers` property, which should contain a map of `<string, HeaderObject>` according to OpenAPI spefication. **These headers will not be automatically validaded for now**
The `input` property is an object containing a `body`, `params` and / or `query` properties, each being a Zod schema. If your request has no inputs you can omit this property. The corresponding `req` properties will be validated and transformed using these schemas. You can also specify a `headers` property, which should contain a map of `<string, HeaderObject>` according to OpenAPI spefication. **These headers will not be automatically validaded for now**

The `output` property is an object literal having one property for each possible status code for that endpoint. Each status code receives a `body` property, which is the Zod schema describing the body of that response. Optionally, each status code can also have a `headers` property, containing the Response headers for that status code. The `res.status().json()` typing will ensure that you use the correct body for the status you choose.

Expand All @@ -28,8 +45,7 @@ You can see more about the OpenAPI specification at [https://swagger.io/](https:

```typescript
import crypto from 'crypto';
import { z } from 'zod';
import { createEndpoint } from '@expresso/router';
import { createEndpoint, z } from '@expresso/router';

type User = { id: string; name: string; email: string; password: string }

Expand Down Expand Up @@ -104,8 +120,96 @@ const createUser = createEndpoint({
})
```

### Zod extension

The router also exports an extension of the Zod lib with an extra method, `openapi`. This method is used to add OpenAPI metadata to the schema, which will be used to generate the swagger documentation. The `openapi` method receives an object with the OpenAPI properties you want to add to the schema.

This feature uses the underlying `extendZodWithOpenApi` function from the [@anatine/zod-openapi](https://www.npmjs.com/package/@anatine/zod-openapi) package. If you want to import your own Zod function, this is also possible, just make sure to use the `extendZodWithOpenApi` function from the same package.

```typescript
import crypto from 'crypto';
import { createEndpoint, z } from '@expresso/router';

type User = { id: string; name: string; email: string; password: string }

const USERS: User[] = []

const createUser = createEndpoint({
description: 'If you call this and a user already exists, it will be shit',
summary: 'Create a new user',
tags: ['Usuários'],
input: {
body: z.object({
name: z.string().min(1).openapi({ description: 'This is the username', example: 'JohnDoe' }),
email: z.string().email().min(1),
password: z.string().min(16)
}),
query: z.object({
testNumber: z
.string()
.refine((s) => !Number.isNaN(Number(s)))
.transform((n) => parseInt(n, 10))
.optional()
.openapi({ default: 100 })
}),
headers: {
authorization: {
description: 'Authorization token'
}
}
},
output: {
201: {
body: z.object({
id: z.string().min(1),
name: z.string().min(1),
email: z.string().email().min(1)
}),
headers: {
'x-content-range': {
description: 'Describes a content range'
}
}
},
409: {
body: z.object({
status: z.literal(409),
message: z.string().min(1)
})
}
},
handlers: [
(_req, _res, next) => {
next()
},
(req, res) => {
const { name, email, password } = req.body

const id = crypto.randomBytes(16).toString('hex')

USERS.push({
id,
name,
email,
password
})

res.status(201).json({
id,
name,
email
})
}
]
})
```

**Note:** All data included in the `openapi` method will not be used for validation, only for documentation purposes.

### Defining routes

#### Simple routes

The routing object has the paths at its main level, with each path having properties for the HTTP methods they handle. The `Routing` type defines a routing object:

```typescript
Expand All @@ -119,7 +223,33 @@ export const routing: Routing = {
};
```

### Putting everything together
#### Nested (prefixed) routes

You can also nest routes by creating a new `Routing` object inside the parent route. This is useful for grouping routes that share a common prefix.

```typescript
import { createUser, updateUser, deleteUser } from './endpoints/create-user.ts';
import { getMe } from './endpoints/me.ts';
import { Routing } from '@expresso/router';

export const routing: Routing = {
'/users': {
'/': {
post: createUser,
},
'/:id': {
get: getUser,
put: updateUser,
delete: deleteUser,
},
},
'/me': {
get: getMe,
}
};
```

## Putting everything together

Now that you have your endpoints and routes, it's time to create the app. The `createApp` function runs an `express` server equipped with the routes and endpoints, plus a `GET /docs` endpoint which renders the swagger UI documentation.

Expand Down
Loading

0 comments on commit 8d0b9d5

Please sign in to comment.