-
-
Notifications
You must be signed in to change notification settings - Fork 127
docs: add example of type-safe error in middleware #1306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
fernando-yaeda
wants to merge
4
commits into
middleapi:main
Choose a base branch
from
fernando-yaeda:docs/type-safe-errors-in-middleware
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
a0a8197
docs: add type-safe errors in middleware example
fernando-yaeda 2fcee11
docs: add Protected Procedures documentation with type-safe error han…
fernando-yaeda 47acaa1
fix(docs): fix grammar and improve sentence clarity
fernando-yaeda f4a0358
docs: fix code block formatting in Protected Procedures documentation
fernando-yaeda File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| --- | ||
| title: Protected Procedures | ||
| description: Learn how to create protected procedures with authentication middleware | ||
| --- | ||
|
|
||
| # Protected Procedures | ||
|
|
||
| You can build protected procedures by defining [errors](/docs/error-handling) in your contract, chaining authentication [middleware](/docs/middleware) on top of an implementer, and then using it in your procedures. | ||
|
|
||
| ## Define Error in Contracts | ||
|
|
||
| Define errors in your contracts using the `.errors()` method. This allows the client to know exactly what errors a procedure can throw. | ||
|
|
||
| ```ts twoslash | ||
| import { oc } from '@orpc/contract' | ||
| import * as z from 'zod' | ||
| // ---cut--- | ||
| export const contract = oc | ||
| .errors({ | ||
| UNAUTHORIZED: { | ||
| message: 'User is not authenticated', | ||
| }, | ||
| }) | ||
| .input( | ||
| z.object({ | ||
| id: z.number() | ||
| }) | ||
| ) | ||
| .output( | ||
| z.object({ | ||
| id: z.number(), | ||
| name: z.string() | ||
| }) | ||
| ) | ||
| ``` | ||
|
|
||
| ## Type-Safe Errors in Middleware | ||
|
|
||
| Middlewares can also throw type-safe errors. However, since not every procedure defined in your contract may contain the error used in the middleware, you must use **type narrowing** to make the error accessible from the `errors` object. | ||
|
|
||
| ```ts | ||
| export const authMiddleware = implement(contract) | ||
| .$context<{ user?: { id: string, email: string } }>() | ||
| .middleware(({ context, next, errors }) => { | ||
| // Type narrowing: Check if UNAUTHORIZED error is defined in the contract | ||
| if (!('UNAUTHORIZED' in errors)) { | ||
| throw new Error('Contract is missing UNAUTHORIZED error') | ||
| } | ||
|
|
||
| if (!context.user) { | ||
| // Throw type-safe error | ||
| throw errors.UNAUTHORIZED() | ||
| } | ||
|
|
||
| return next({ | ||
| context: { | ||
| user: context.user, | ||
| }, | ||
| }) | ||
| }) | ||
| ``` | ||
|
|
||
| ## Setting Up Protected Procedures | ||
|
|
||
| Start by creating a public implementer with your contract. Then extend it with an authentication middleware to create the protected one: | ||
|
|
||
| ```ts | ||
| export const pub = implement(contract) | ||
| .use(dbProviderMiddleware) | ||
|
|
||
| export const authed = pub | ||
| .use(authMiddleware) | ||
| ``` | ||
|
|
||
| :::info | ||
| By using `pub.use(authMiddleware)`, the protected implementer inherits all middleware from the public implementer and adds authentication on top. | ||
| ::: | ||
|
|
||
| ## Protect the Procedure | ||
|
|
||
| Now use `pub` for public procedures and `authed` for protected ones in your router: | ||
|
|
||
| ```ts | ||
| // Public procedure - no authentication required | ||
| export const listPlanets = pub.planet.list | ||
| .handler(async ({ input, context }) => { | ||
| return context.db.planets.list(input.limit, input.cursor) | ||
| }) | ||
|
|
||
| // Protected procedure - requires authentication | ||
| export const createPlanet = authed.planet.create | ||
| .handler(async ({ input, context }) => { | ||
| // context.user is guaranteed to exist here due to authMiddleware | ||
| return context.db.planets.create(input, context.user) | ||
| }) | ||
| ``` | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { implement } from '@orpc/server' | ||
fernando-yaeda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import { contract } from '../contract' | ||
| import type { UserSchema } from '../schemas/user' | ||
| import type { z } from 'zod' | ||
|
|
||
| export interface AuthContext { | ||
| user?: z.infer<typeof UserSchema> | ||
| } | ||
|
|
||
| export const authMiddleware = implement(contract) | ||
| .$context<AuthContext>() | ||
| .middleware(({ context, next, errors }) => { | ||
| /** | ||
| * Type narrowing check for error constructor access. | ||
| * Required when not all procedures in the contract define this error. | ||
| */ | ||
| if (!('UNAUTHORIZED' in errors)) { | ||
| throw new Error('Contract is missing UNAUTHORIZED error') | ||
| } | ||
|
|
||
| if (!context.user) { | ||
| throw errors.UNAUTHORIZED() | ||
| } | ||
|
|
||
| return next({ | ||
| context: { | ||
| user: context.user, | ||
| }, | ||
| }) | ||
| }) | ||
fernando-yaeda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,10 @@ | ||
| import type { z } from 'zod' | ||
| import type { UserSchema } from './schemas/user' | ||
| import { implement, ORPCError } from '@orpc/server' | ||
| import { implement } from '@orpc/server' | ||
| import { dbProviderMiddleware } from './middlewares/db' | ||
| import { contract } from './contract' | ||
|
|
||
| export interface ORPCContext { | ||
| user?: z.infer<typeof UserSchema> | ||
| } | ||
| import { authMiddleware } from './middlewares/auth' | ||
|
|
||
| export const pub = implement(contract) | ||
| .$context<ORPCContext>() | ||
| .use(dbProviderMiddleware) | ||
|
|
||
| export const authed = pub.use(({ context, next }) => { | ||
| if (!context.user) { | ||
| throw new ORPCError('UNAUTHORIZED') | ||
| } | ||
|
|
||
| return next({ | ||
| context: { | ||
| user: context.user, | ||
| }, | ||
| }) | ||
| }) | ||
| export const authed = pub | ||
| .use(authMiddleware) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to clarify that type-safe errors are an optional pattern, not a requirement. Because of that, I don’t think the “Protected Procedure” title is suitable here, or alternatively, the content is missing the approach for handling a normal ORPCError.
I think we should roll back the changes made in the playground and only mention type-safe errors as an optional approach, rather than treating them as the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes sense.
Should I proceed with reverting the playground changes and focus on the documentation changes by adding the normal ORPCError approach as a default and adding the type-safe errors as an option?