#
Error Handling
Latest
#
Orchestrator
Error handling within the orchestrator is expected to conform to the same standards as those employed in the Nuxt ecosystem.
Use the createError utility, provided by h3, to throw an error. There are several rules to keep in mind when composing the error object:
- Use a response status code that accurately conveys the issue, in the
statusproperty - Clearly explain the issue in the
messageproperty - Do not put sensitive data to the
messageproperty
#
IO
Input (router parameters, query or body) and output (body) of each endpoint has to be validated to match their corresponding Zod schema.
#
Input
The h3 also provides utilities for validating the router parameters, query or body.
Use getValidatedRouterParams, getValidatedQuery or readValidatedBody utilities to validate them.
// File: apps/gateway/server/api/v1/organization/users/[id]
// Define router parameters schema
const routerParamsSchema = z.object({
id: z.uuid(),
})
// Create an interface extending inferred type from the schema
interface DeleteOrganizationUserParams extends z.infer<typeof routerParamsSchema> {}
// Put the interface as a generics parameter of the `getValidatedRouterParams`
const { id } = await getValidatedRouterParams<DeleteOrganizationUserParams>(
event,
routerParamsSchema.parse, // Provide the `parse` property of the schema
)
// File: apps/gateway/server/api/v1/transactions/deposit.get.ts
// Define query schema
export const querySchema = z.object({
currency: z.enum(CurrencyZod),
})
// Create an interface extending inferred type from the schema
interface DepositQuery extends z.infer<typeof querySchema> {}
// Put the interface as a generics parameter of the `getValidatedRouterParams`
const { currency } = await getValidatedQuery<DepositQuery>(
event,
querySchema.parse, // Provide the `parse` property of the schema
)
// File: apps/gateway/server/api/v1/organization/users/index.post.ts
// Define body schema
const requestBodySchema = z.object({
email: z.email(),
role: z.enum(UserRoleZod),
})
// Create an interface extending inferred type from the schema
interface CreateOrganizationUserRequestBody extends z.infer<typeof requestBodySchema> {}
// Put the interface as a generics parameter of the `getValidatedRouterParams`
const { role, email } = await readValidatedBody<CreateOrganizationUserRequestBody>(
event,
requestBodySchema.parse, // Provide the `parse` property of the schema
)
#
Output
Nitro offers a native utility for defining endpoints, but use the ⚙️ defineGuardEventHandler utility instead, as it provides much needed guard functionality. This utility accepts a generics parameter that specifies the return type of the endpoint. Output validation is performed during static type checking.
As the response of the endpoint has to be the same across the gateway, response body is formed into the standardized response object ⚙️ GatewayResponse.
interface GatewayResponse<T> {
status?: number // Represents the HTTP response code
message: string // Represents the HTTP response message
data?: T // The actual data to be returned
}
// Define response schema
const responseBodySchema = z.object({
id: z.uuid(),
email: z.email(),
role: z.enum(UserRoleZod),
verified: z.boolean(),
})
// Create an interface extending inferred type from the schema
interface CreateOrganizationUserResponse extends GatewayResponse<z.infer<typeof responseBodySchema>> {}
// Put the interface as a generics parameter of the `defineGuardEventHandler`
export default defineGuardEventHandler<CreateOrganizationUserResponse>({ guards: [] }, async (event) => {
// ...
return {
message: 'Organization user successfully created.',
data: {
id: userData.id,
email: userData.email,
role: userData.role,
verified: userData.verified,
},
}
},
)
#
Action Response
Handling the response of an action is a straightforward process, as the call is wrapped within the ⚙️ handleActionResponse utility, which either throws an error or returns the actual data, thereby eliminating the need for null checks.
Given that each action returns a standardized object, the ⚙️ handleActionResponse utility can readily determine whether the action call was successful or not. Make sure an action always returns a truthy value, use an empty object, instead of null.
interface ActionResponse {
error: boolean; // Indicates whether the action was successful
status: number; // Represents the HTTP response code
message: string; // Represents the HTTP response message
data: T; // The actual data returned by the action
}
// Create a new user for the organization
const userData = handleActionResponse(
await event.context.cloudflare.env.ORG_SERVICE.createOrganizationUser({
organizationId: event.context.user.organizationId,
creatorRole: event.context.user.role,
createdRole: role,
email,
}),
)
#
Service
Actual implementation of an action resides on the service. It is itended to be try-catch-less as an error is caught automatically and returned as the second positional parameter of the ⚙️ useResult function.
const [data, error] = await useResult(actionExecution())
Such an error should be later returned utilizing ⚙️ serviceError function.
return RPCResponse.serviceError(error)
#
InternalError
An error can also be explicitly thrown inside the promise by utilizing the ⚙️ createInternalError function. The returned object should then be returned using the serviceError function as well, as mentioned above.
throw createInternalError(null, { status: 418, message: 'This message and status will be available in the response object.' })
#
ValidationError
Workers SDK also provides an input validation function, utilizing Zod, which should be called as the first step within the action. If validation fails, an internal error is returned, and should be immediately returned as a ValidationError using the validationError function.
const validationError = validateRPCInput(input, actionInputSchema)
if (validationError) {
return Utils.RPCResponse.validationError(validationError)
}
#
Example
This concludes the essential information for handling errors in an action correctly. Below is an example demonstrating basic error handling within the action.
const validationError = validateRPCInput(input, actionInputSchema)
if (validationError) {
return RPCResponse.validationError(validationError)
}
const { userId } = input
const promise = async () => {
// Get the user
const user = await getUserById(this.db, userId)
if (!user) {
throw createInternalError(null, { status: 404, message: `User with ID '${userId}' not found.` })
}
return user
}
const [data, error] = await useResult(promise())
return error ? serviceError(error) : ok('User successfully obtained.', { data })