mirror of
https://github.com/ArthurDanjou/trpc-nuxt.git
synced 2026-01-24 08:50:32 +01:00
docs: improve docs
This commit is contained in:
44
docs/content/1.get-started/4.tips/1.composables.md
Normal file
44
docs/content/1.get-started/4.tips/1.composables.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Composables
|
||||
---
|
||||
|
||||
# Composables
|
||||
|
||||
It is often useful to wrap functionality of your `@trpc/client` api within other functions. For this purpose, it's necessary to be able to infer input types and output types generated by your `@trpc/server` router.
|
||||
|
||||
::alert{type="info"}
|
||||
[createTRPCNuxtClient](/get-started/client/create) adds a `useQuery` method built on top of [useAsyncData](https://nuxt.com/docs/api/composables/use-async-data/).
|
||||
::
|
||||
|
||||
## Inference Helpers
|
||||
|
||||
`@trpc/server` exports the following helper types to assist with inferring these types from the `AppRouter` exported by your `@trpc/server` router:
|
||||
|
||||
- `inferRouterInputs<TRouter>`
|
||||
- `inferRouterOutputs<TRouter>`
|
||||
|
||||
Let's assume we have this example query wrapped within Nuxt's [useAsyncData](https://v3.nuxtjs.org/api/composables/use-async-data/):
|
||||
|
||||
```ts
|
||||
const { data, error } = await useAsyncData(() => $client.todo.getTodos.query())
|
||||
```
|
||||
|
||||
We can wrap this in a composable and also set the client error types:
|
||||
|
||||
```ts [composables/useGetTodos.ts]
|
||||
import { TRPCClientError } from '@trpc/client'
|
||||
import type { inferRouterOutputs } from '@trpc/server'
|
||||
import type { AppRouter } from '@/server/trpc/routers'
|
||||
|
||||
type RouterOutput = inferRouterOutputs<AppRouter>
|
||||
type GetTodosOutput = RouterOutput['todo']['getTodos']
|
||||
|
||||
type ErrorOutput = TRPCClientError<AppRouter>
|
||||
|
||||
export default function useGetTodos() {
|
||||
const { $client } = useNuxtApp()
|
||||
return useAsyncData<GetTodosOutput, ErrorOutput>(() => $client.todo.getTodos.query())
|
||||
}
|
||||
```
|
||||
|
||||
Now, we have a fully-typed composable.
|
||||
44
docs/content/1.get-started/4.tips/2.headers.md
Normal file
44
docs/content/1.get-started/4.tips/2.headers.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Headers
|
||||
---
|
||||
|
||||
# Headers
|
||||
|
||||
We can use the built-in [useRequestHeaders](https://v3.nuxtjs.org/api/composables/use-request-headers/) to set outgoing request headers:
|
||||
|
||||
::alert{type="info"}
|
||||
[createTRPCNuxtClient](/get-started/client/create) has this feature by default.
|
||||
::
|
||||
|
||||
```ts [plugins/client.ts]
|
||||
export default defineNuxtPlugin(() => {
|
||||
const headers = useRequestHeaders()
|
||||
|
||||
const client = createTRPCProxyClient<AppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
// headers need to be a function so it gets called dynamically
|
||||
// every HTTP request
|
||||
headers() {
|
||||
// You can add more custom headers here
|
||||
return headers
|
||||
}
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
return {
|
||||
provide: {
|
||||
client,
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
```ts [server/trpc/context.ts]
|
||||
export function createContext (event: H3Event) {
|
||||
console.log('cookies', parseCookies(event))
|
||||
|
||||
return {}
|
||||
}
|
||||
```
|
||||
103
docs/content/1.get-started/4.tips/3.authorization.md
Normal file
103
docs/content/1.get-started/4.tips/3.authorization.md
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
title: Authorization
|
||||
---
|
||||
|
||||
# Authorization
|
||||
|
||||
The `createContext` function is called for each incoming request so here you can add contextual information about the calling user from the request object.
|
||||
|
||||
::alert{type="warning"}
|
||||
Before you can access request headers in any context or middleware, you need to set the outgoing request headers. See [here](/get-started/tips/headers).
|
||||
::
|
||||
|
||||
## Create context from request headers
|
||||
|
||||
```ts [server/trpc/context.ts]
|
||||
import { inferAsyncReturnType } from '@trpc/server'
|
||||
import { decodeAndVerifyJwtToken } from './somewhere/in/your/app/utils'
|
||||
|
||||
export async function createContext(event: H3Event) {
|
||||
// Create your context based on the request object
|
||||
// Will be available as `ctx` in all your resolvers
|
||||
|
||||
// This is just an example of something you might want to do in your ctx fn
|
||||
const authorization = getRequestHeader(event, 'authorization')
|
||||
async function getUserFromHeader() {
|
||||
if (authorization) {
|
||||
const user = await decodeAndVerifyJwtToken(authorization.split(' ')[1])
|
||||
return user
|
||||
}
|
||||
return null
|
||||
}
|
||||
const user = await getUserFromHeader()
|
||||
|
||||
return {
|
||||
user,
|
||||
}
|
||||
}
|
||||
type Context = inferAsyncReturnType<typeof createContext>
|
||||
```
|
||||
|
||||
## Option 1: Authorize using resolver
|
||||
|
||||
```ts
|
||||
import { TRPCError, initTRPC } from '@trpc/server'
|
||||
import type { Context } from '../context'
|
||||
|
||||
export const t = initTRPC.context<Context>().create()
|
||||
|
||||
const appRouter = t.router({
|
||||
// open for anyone
|
||||
hello: t.procedure
|
||||
.input(z.string().nullish())
|
||||
.query(({ input, ctx }) => `hello ${input ?? ctx.user?.name ?? 'world'}`),
|
||||
// checked in resolver
|
||||
secret: t.procedure.query(({ ctx }) => {
|
||||
if (!ctx.user) {
|
||||
throw new TRPCError({ code: 'UNAUTHORIZED' })
|
||||
}
|
||||
return {
|
||||
secret: 'sauce',
|
||||
}
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
## Option 2: Authorize using middleware
|
||||
|
||||
```ts
|
||||
import { TRPCError, initTRPC } from '@trpc/server'
|
||||
|
||||
export const t = initTRPC.context<Context>().create()
|
||||
|
||||
const isAuthed = t.middleware(({ next, ctx }) => {
|
||||
if (!ctx.user?.isAdmin) {
|
||||
throw new TRPCError({ code: 'UNAUTHORIZED' })
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
user: ctx.user,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// you can reuse this for any procedure
|
||||
export const protectedProcedure = t.procedure.use(isAuthed)
|
||||
|
||||
t.router({
|
||||
// this is accessible for everyone
|
||||
hello: t.procedure
|
||||
.input(z.string().nullish())
|
||||
.query(({ input, ctx }) => `hello ${input ?? ctx.user?.name ?? 'world'}`),
|
||||
admin: t.router({
|
||||
// this is accessible only to admins
|
||||
secret: protectedProcedure.query(({ ctx }) => {
|
||||
return {
|
||||
secret: 'sauce',
|
||||
}
|
||||
}),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
This page is entirely based on [authorization docs](https://trpc.io/docs/v10/authorization) of tRPC with a minimal change made to work with Nuxt.
|
||||
84
docs/content/1.get-started/4.tips/4.server-side-calls.md
Normal file
84
docs/content/1.get-started/4.tips/4.server-side-calls.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
title: Server Side Calls
|
||||
---
|
||||
|
||||
# Server Side Calls
|
||||
|
||||
You may need to call your procedure(s) directly from the server, `createCaller()` function returns you an instance of `RouterCaller` able to execute queries and mutations.
|
||||
|
||||
## Input query example
|
||||
|
||||
We create the router with a input query and then we call the asynchronous `greeting` procedure to get the result.
|
||||
|
||||
::code-group
|
||||
|
||||
```ts [server/trpc/trpc.ts]
|
||||
import { initTRPC } from '@trpc/server'
|
||||
import { z } from 'zod'
|
||||
|
||||
const t = initTRPC.create()
|
||||
|
||||
export const router = t.router({
|
||||
// Create procedure at path 'greeting'
|
||||
greeting: t.procedure
|
||||
.input(z.object({ name: z.string() }))
|
||||
.query(({ input }) => `Hello ${input.name}`),
|
||||
})
|
||||
```
|
||||
|
||||
```ts [server/api/greeting.ts]
|
||||
import { router } from '@/server/trpc/trpc'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const { name } = getQuery(event)
|
||||
const caller = router.createCaller({})
|
||||
|
||||
const greeting = await caller.greeting({ name })
|
||||
|
||||
return {
|
||||
greeting
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
## Mutation example
|
||||
|
||||
We create the router with a mutation and then we call the asynchronous `post` procedure to get the result.
|
||||
|
||||
::code-group
|
||||
|
||||
```ts [server/trpc/trpc.ts]
|
||||
import { initTRPC } from '@trpc/server'
|
||||
import { z } from 'zod'
|
||||
|
||||
const posts = ['One', 'Two', 'Three']
|
||||
|
||||
const t = initTRPC.create()
|
||||
export const router = t.router({
|
||||
post: t.router({
|
||||
add: t.procedure.input(z.string()).mutation(({ input }) => {
|
||||
posts.push(input)
|
||||
return posts
|
||||
}),
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
```ts [server/api/post.ts]
|
||||
import { router } from '@/server/trpc/trpc'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const body = await getBody(event)
|
||||
const caller = router.createCaller({})
|
||||
|
||||
const post = await caller.post.add(body.post)
|
||||
|
||||
return {
|
||||
post
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
::
|
||||
24
docs/content/1.get-started/4.tips/5.aborting-procedures.md
Normal file
24
docs/content/1.get-started/4.tips/5.aborting-procedures.md
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Aborting Procedures
|
||||
---
|
||||
|
||||
# Aborting Procedures
|
||||
|
||||
tRPC adheres to the industry standard when it comes to aborting procedures. All you have to do is pass an `AbortSignal` to the query-options and then call its parent `AbortController`'s `abort` method.
|
||||
|
||||
```ts [composables/useGetTodo.ts]
|
||||
export default function useGetTodo(id: number) {
|
||||
const { $client } = useNuxtApp()
|
||||
const ac = new AbortController()
|
||||
|
||||
onScopeDispose(() => {
|
||||
ac.abort()
|
||||
})
|
||||
|
||||
return useAsyncData(() => {
|
||||
return $client.todo.getTodo.query(id, {
|
||||
signal: ac.signal
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user