218 lines
6.0 KiB
Plaintext
218 lines
6.0 KiB
Plaintext
---
|
|
description: Middlewares are used to execute code before and after the handler is called
|
|
globs: steps/**/*.step.ts,steps/**/*.step.js,steps/**/*_step.py,middlewares/**/*.middleware.ts,middlewares/**/*.middleware.js,middlewares/**/*_middleware.py
|
|
alwaysApply: false
|
|
---
|
|
# Middlewares Guide
|
|
|
|
Middlewares are used to execute code before and after the handler is called in API Steps.
|
|
|
|
The middleware is a handler that receives three arguments:
|
|
- **Request**: this is the same request object received by API Step handlers, if modified by the middleware, it will be the same object passed to the handler and any subsequent middleware. Be careful to not cause any side effects to the request object.
|
|
- **Context**: this is the same context object received by API Step handlers, if modified by the middleware, it will be the same object passed to the handler and any subsequent middleware. Be careful to not cause any side effects to the context object.
|
|
- **Next**: this is a function that you need to call to invoke the next middleware in the stack. If you don't call it, the request will be halted—the handler and any subsequent middlewares will not be called.
|
|
|
|
## Next function
|
|
|
|
Next function is a way to either continue the execution flow or stop it. For example, in authentication middlewares, if the user is not authenticated, you can return a 401 response and not call `next()`.
|
|
|
|
It can also be used to enrich data returned back to the HTTP response. Like adding a header parameter or so after calling `next()`.
|
|
|
|
## Adding middlewares to a step
|
|
|
|
### TypeScript/JavaScript Example
|
|
|
|
```typescript
|
|
import { ApiRouteConfig } from 'motia'
|
|
import { coreMiddleware } from '../middlewares/core.middleware'
|
|
|
|
export const config: ApiRouteConfig = {
|
|
type: 'api',
|
|
name: 'SampleRoute',
|
|
description: 'Sample route',
|
|
path: '/sample',
|
|
method: 'GET',
|
|
emits: [],
|
|
flows: [],
|
|
middleware: [coreMiddleware],
|
|
}
|
|
```
|
|
|
|
### Python Example
|
|
|
|
```python
|
|
async def enrich_data_middleware(req, context, next_fn):
|
|
context.logger.info("enriching data")
|
|
req["enriched"] = "yes"
|
|
return await next_fn()
|
|
|
|
config = {
|
|
"type": "api",
|
|
"name": "SampleRoute",
|
|
"description": "Sample route",
|
|
"path": "/sample",
|
|
"method": "GET",
|
|
"emits": [],
|
|
"flows": [],
|
|
"middleware": [enrich_data_middleware],
|
|
}
|
|
```
|
|
|
|
## Middleware examples
|
|
|
|
### Handling errors
|
|
|
|
#### TypeScript/JavaScript
|
|
|
|
```typescript
|
|
import { ApiMiddleware } from 'motia'
|
|
|
|
export const coreMiddleware: ApiMiddleware = async (req, ctx, next) => {
|
|
const { logger } = ctx
|
|
|
|
try {
|
|
/**
|
|
* Calling next() will invoke the next item in the stack.
|
|
*
|
|
* It will depend on the order of middlewares configured in the step,
|
|
* first one in the list is called first and so on.
|
|
*/
|
|
return await next()
|
|
} catch (error: any) {
|
|
logger.error('Error while performing request', {
|
|
error,
|
|
body: req.body, // make sure you don't include sensitive data in the logs
|
|
stack: error.stack,
|
|
})
|
|
|
|
return {
|
|
status: 500,
|
|
body: { error: 'Internal Server Error' },
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Python
|
|
|
|
```python
|
|
async def error_handling_middleware(req, context, next_fn):
|
|
try:
|
|
# Calling next_fn() will invoke the next item in the stack.
|
|
# It will depend on the order of middlewares configured in the step,
|
|
# first one in the list is called first and so on.
|
|
return await next_fn()
|
|
except Exception as error:
|
|
context.logger.error('Error while performing request', {
|
|
'error': str(error),
|
|
'body': req.get('body'), # make sure you don't include sensitive data in the logs
|
|
})
|
|
|
|
return {
|
|
'status': 500,
|
|
'body': {'error': 'Internal Server Error'},
|
|
}
|
|
```
|
|
|
|
### Enriching response
|
|
|
|
#### TypeScript/JavaScript
|
|
|
|
```typescript
|
|
export const enrichResponseMiddleware: ApiMiddleware = async (req, ctx, next) => {
|
|
const response = await next()
|
|
|
|
response.headers['X-Custom-Header'] = 'Custom Value'
|
|
|
|
return response
|
|
}
|
|
```
|
|
|
|
#### Python
|
|
|
|
```python
|
|
async def enrich_response_middleware(req, context, next_fn):
|
|
response = await next_fn()
|
|
|
|
if not response.get('headers'):
|
|
response['headers'] = {}
|
|
|
|
response['headers']['X-Custom-Header'] = 'Custom Value'
|
|
|
|
return response
|
|
```
|
|
|
|
### Handling validation errors
|
|
|
|
#### TypeScript/JavaScript - Handling Zod Validation errors
|
|
|
|
```typescript
|
|
import { ApiMiddleware } from 'motia'
|
|
import { ZodError } from 'zod'
|
|
|
|
export const coreMiddleware: ApiMiddleware = async (req, ctx, next) => {
|
|
const logger = ctx.logger
|
|
|
|
try {
|
|
return await next()
|
|
} catch (error: any) {
|
|
if (error instanceof ZodError) {
|
|
logger.error('Validation error', {
|
|
error,
|
|
stack: error.stack,
|
|
errors: error.errors,
|
|
})
|
|
|
|
return {
|
|
status: 400,
|
|
body: {
|
|
error: 'Invalid request body',
|
|
data: error.errors,
|
|
},
|
|
}
|
|
}
|
|
|
|
logger.error('Error while performing request', {
|
|
error,
|
|
body: req.body, // make sure you don't include sensitive data in the logs
|
|
stack: error.stack,
|
|
})
|
|
|
|
return { status: 500, body: { error: 'Internal Server Error' } }
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Python - Handling Pydantic Validation errors
|
|
|
|
```python
|
|
from pydantic import ValidationError
|
|
|
|
async def validation_middleware(req, context, next_fn):
|
|
try:
|
|
return await next_fn()
|
|
except ValidationError as error:
|
|
context.logger.error('Validation error', {
|
|
'error': str(error),
|
|
'errors': error.errors(),
|
|
})
|
|
|
|
return {
|
|
'status': 400,
|
|
'body': {
|
|
'error': 'Invalid request body',
|
|
'data': error.errors(),
|
|
},
|
|
}
|
|
except Exception as error:
|
|
context.logger.error('Error while performing request', {
|
|
'error': str(error),
|
|
'body': req.get('body'), # make sure you don't include sensitive data in the logs
|
|
})
|
|
|
|
return {
|
|
'status': 500,
|
|
'body': {'error': 'Internal Server Error'}
|
|
}
|
|
```
|