diff --git a/README.md b/README.md index 11d553d..572ecc2 100644 --- a/README.md +++ b/README.md @@ -4,162 +4,7 @@ End-to-end typesafe APIs with [tRPC.io](https://trpc.io/) in Nuxt applications. -

-

- Demo -
-

- The client above is not importing any code from the server, only its type declarations. -

-
-
-

- -## Install - -```bash -npm i trpc-nuxt -``` - -```ts -// nuxt.config.ts -import { defineNuxtConfig } from 'nuxt' - -export default defineNuxtConfig({ - modules: ['trpc-nuxt'], - trpc: { - baseURL: '', // Set empty string (default) to make requests by relative address - endpoint: '/trpc', // defaults to /trpc - }, - typescript: { - strict: true // required to make input/output types work - } -}) -``` - -## Usage - -Expose your tRPC [routes](https://trpc.io/docs/router) under `~/server/trpc/index.ts`: - -```ts -// ~/server/trpc/index.ts -import type { inferAsyncReturnType } from '@trpc/server' -import * as trpc from '@trpc/server' -import { z } from 'zod' // yup/superstruct/zod/myzod/custom - -export const router = trpc.router() - // queries and mutations... - .query('getUsers', { - async resolve(req) { - // use your ORM of choice - return await UserModel.all() - }, - }) - .mutation('createUser', { - // validate input with Zod - input: z.object({ name: z.string().min(5) }), - async resolve(req) { - // use your ORM of choice - return await UserModel.create({ - data: req.input, - }) - }, - }) -``` - -Use the client like so: - -```ts -const client = useClient() // auto-imported - -const users = await client.query('getUsers') - -const newUser = await client.mutation('createUser', { - name: 'wagmi' -}) -``` - -## useAsyncQuery - -A thin wrapper around [`useAsyncData`](https://v3.nuxtjs.org/api/composables/use-async-data/) and `client.query()`. - -The first argument is a `[path, input]`-tuple - if the `input` is optional, you can omit the, `input`-part. - -You'll notice that you get autocompletion on the `path` and automatic typesafety on the `input`. - -```ts -const { - data, - pending, - error, - refresh -} = await useAsyncQuery(['getUser', { id: 69 }], { - // pass useAsyncData options here - lazy: false -}) -``` - -## useClientHeaders - -A composable that lets you add additional properties to pass to the tRPC Client. It uses `useState` from [nuxt 3](https://v3.nuxtjs.org/api/composables/use-state). - -```ts -const headers = useClientHeaders() - -const { data: token } = await useAsyncQuery(['auth.login', { username, password }]) - -headers.value.Authorization = `Bearer ${token}` - -// All client calls will now include the Authorization header. -``` - -## Options - -trpc-nuxt accepts the following options exposed under `~/server/trpc/index.ts`: - -```ts -import * as trpc from '@trpc/server' -import type { inferAsyncReturnType } from '@trpc/server' -import type { H3Event } from 'h3' -import type { OnErrorPayload } from 'trpc-nuxt/api' - -export const router = trpc.router>() - -// Optional -// https://trpc.io/docs/context -export const createContext = (event: H3Event) => { - // ... - return { - /** context data */ - } -} - -// Optional -// https://trpc.io/docs/caching#using-responsemeta--to-cache-responses -export const responseMeta = () => { - // ... - return { - // { headers: ... } - } -} - -// Optional -// https://trpc.io/docs/error-handling#handling-errors -export const onError = (payload: OnErrorPayload) => { - // Do whatever here like send to bug reporting and stuff -} -``` - -## Recipes - -- [Validation](/recipes/validation.md) -- [Authorization](/recipes/authorization.md) -- [Merging Routers](/recipes/merging-routers.md) -- [Error Handling](/recipes/error-handling.md) -- [Error Formatting](/recipes/error-formatting.md) -- [Inference Helpers](/recipes/inference-helpers.md) - -Learn more about tRPC.io [here](https://trpc.io/docs/v9). +Learn more about tRPC.io [here](https://trpc.io/docs/v10). ## Recommended IDE Setup diff --git a/recipes/authorization.md b/recipes/authorization.md deleted file mode 100644 index 3d83b83..0000000 --- a/recipes/authorization.md +++ /dev/null @@ -1,102 +0,0 @@ -## 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. - -### Create context from request headers - -```ts -// ~/server/trpc/index.ts -import type { inferAsyncReturnType } from '@trpc/server' -import type { H3Event } from 'h3' -import { decodeAndVerifyJwtToken } from '~/somewhere/in/your/app/utils' - -// The app's context - is generated for each incoming request -export async function createContext({ req }: 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'd might want to do in your ctx fn - async function getUserFromHeader() { - if (req.headers.authorization) { - const user = await decodeAndVerifyJwtToken(req.headers.authorization.split(' ')[1]) - return user - } - return null - } - const user = await getUserFromHeader() - - return { - user, - } -} - -type Context = inferAsyncReturnType - -// [..] Define API handler and app router -``` - -### Option 1: Authorize using resolver - -```ts -import { TRPCError } from '@trpc/server' - -export const router = trpc - .router() - // open for anyone - .query('hello', { - input: z.string().nullish(), - resolve: ({ input, ctx }) => { - return `hello ${input ?? ctx.user?.name ?? 'world'}` - }, - }) - // checked in resolver - .query('secret', { - resolve: ({ ctx }) => { - if (!ctx.user) - throw new TRPCError({ code: 'UNAUTHORIZED' }) - - return { - secret: 'sauce', - } - }, - }) -``` - -### Option 2: Authorize using middleware - -```ts -import * as trpc from '@trpc/server' -import { TRPCError } from '@trpc/server' - -// Merging routers: https://trpc.io/docs/merging-routers - -export const router = trpc - .router() - // this is accessible for everyone - .query('hello', { - input: z.string().nullish(), - resolve: ({ input, ctx }) => { - return `hello ${input ?? ctx.user?.name ?? 'world'}` - }, - }) - .merge( - 'admin.', - trpc.router() - // this protects all procedures defined next in this router - .middleware(async ({ ctx, next }) => { - if (!ctx.user?.isAdmin) - throw new TRPCError({ code: 'UNAUTHORIZED' }) - - return next() - }) - .query('secret', { - resolve: ({ ctx }) => { - return { - secret: 'sauce', - } - }, - }), - ) -``` - -Learn more about authorization [here](https://trpc.io/docs/authorization). diff --git a/recipes/error-formatting.md b/recipes/error-formatting.md deleted file mode 100644 index b60290c..0000000 --- a/recipes/error-formatting.md +++ /dev/null @@ -1,41 +0,0 @@ -## Error Formatting - -The error formatting in your router will be inferred all the way to your client (& Vue components). - -### Adding custom formatting - -```ts -// ~/server/trpc/index.ts -import * as trpc from '@trpc/server' - -export const router = trpc.router() - .formatError(({ shape, error }) => { - return { - ...shape, - data: { - ...shape.data, - zodError: - error.code === 'BAD_REQUEST' - && error.cause instanceof ZodError - ? error.cause.flatten() - : null, - } - } - }) -``` - -### Usage in Vue - -```html - - - -``` - -Learn more about error formatting [here](https://trpc.io/docs/error-formatting). diff --git a/recipes/error-handling.md b/recipes/error-handling.md deleted file mode 100644 index 1821349..0000000 --- a/recipes/error-handling.md +++ /dev/null @@ -1,15 +0,0 @@ -## Handling errors - -All errors that occur in a procedure go through the `onError` method before being sent to the client. Here you can handle or change errors. - -```ts -// ~/server/trpc/index.ts -import * as trpc from '@trpc/server' - -export function onError({ error, type, path, input, ctx, req }) { - console.error('Error:', error) - if (error.code === 'INTERNAL_SERVER_ERROR') { - // send to bug reporting - } -} -``` diff --git a/recipes/inference-helpers.md b/recipes/inference-helpers.md deleted file mode 100644 index 78bf4d4..0000000 --- a/recipes/inference-helpers.md +++ /dev/null @@ -1,80 +0,0 @@ -## Inference Helpers - -`@trpc/server` exports the following helper types to assist with inferring these types from the `router` exported in `~/server/trpc/index.ts`: - -- `inferProcedureOutput` -- `inferProcedureInput` -- `inferSubscriptionOutput` - -```ts -// ~/utils/trpc.ts -import type { router } from '~/server/trpc/index.ts' - -type AppRouter = typeof router - -/** - * Enum containing all api query paths - */ -export type TQuery = keyof AppRouter['_def']['queries'] - -/** - * Enum containing all api mutation paths - */ -export type TMutation = keyof AppRouter['_def']['mutations'] - -/** - * Enum containing all api subscription paths - */ -export type TSubscription = keyof AppRouter['_def']['subscriptions'] - -/** - * This is a helper method to infer the output of a query resolver - * @example type HelloOutput = InferQueryOutput<'hello'> - */ -export type InferQueryOutput = inferProcedureOutput< - AppRouter['_def']['queries'][TRouteKey] -> - -/** - * This is a helper method to infer the input of a query resolver - * @example type HelloInput = InferQueryInput<'hello'> - */ -export type InferQueryInput = inferProcedureInput< - AppRouter['_def']['queries'][TRouteKey] -> - -/** - * This is a helper method to infer the output of a mutation resolver - * @example type HelloOutput = InferMutationOutput<'hello'> - */ -export type InferMutationOutput = - inferProcedureOutput - -/** - * This is a helper method to infer the input of a mutation resolver - * @example type HelloInput = InferMutationInput<'hello'> - */ -export type InferMutationInput = - inferProcedureInput - -/** - * This is a helper method to infer the output of a subscription resolver - * @example type HelloOutput = InferSubscriptionOutput<'hello'> - */ -export type InferSubscriptionOutput = - inferProcedureOutput - -/** - * This is a helper method to infer the asynchronous output of a subscription resolver - * @example type HelloAsyncOutput = InferAsyncSubscriptionOutput<'hello'> - */ -export type InferAsyncSubscriptionOutput = - inferSubscriptionOutput - -/** - * This is a helper method to infer the input of a subscription resolver - * @example type HelloInput = InferSubscriptionInput<'hello'> - */ -export type InferSubscriptionInput = - inferProcedureInput -``` diff --git a/recipes/merging-routers.md b/recipes/merging-routers.md deleted file mode 100644 index 5c93328..0000000 --- a/recipes/merging-routers.md +++ /dev/null @@ -1,46 +0,0 @@ -# Merging Routers - -Writing all API-code in your code in the same file is not a great idea. It's easy to merge routers with other routers. - -Define your routes: - -```ts -// ~/server/trpc/routes/posts.ts -export const posts = trpc.router() - .query('list', { - resolve() { - // .. - return [] - } - }) -``` - -```ts -// ~/server/trpc/routes/users.ts -export const users = trpc.router() - .query('list', { - resolve() { - // .. - return [] - } - }) -``` - -```ts -// ~/server/trpc/index.ts -import { users } from './routes/users' -import { posts } from './routes/posts' - -export const router = trpc.router() - .merge('user.', users) // prefix user procedures with "user." - .merge('post.', posts) // prefix post procedures with "post." -``` - -and use it like this: - -```html - -``` diff --git a/recipes/validation.md b/recipes/validation.md deleted file mode 100644 index dd810f1..0000000 --- a/recipes/validation.md +++ /dev/null @@ -1,49 +0,0 @@ -## Validation - -tRPC works out-of-the-box with yup/superstruct/zod/myzod/custom validators. - -### Input Validation - -```ts -// ~/server/trpc/index.ts -import { z } from 'zod' - -export const router = trpc - .router() - .mutation('createUser', { - // validate input with Zod - input: z.object({ - name: z.string().min(5) - }), - async resolve(req) { - // use your ORM of choice - return await UserModel.create({ - data: req.input, - }) - }, - }) -``` - -### Output Validation - -```ts -// ~/server/trpc/index.ts -import { z } from 'zod' - -export const router = trpc - .router() - .query('hello', { - // validate output with Zod - output: z.object({ - greeting: z.string() - }), - // expects return type of { greeting: string } - resolve() { - return { - greeting: 'hello!', - } - }, - }) -``` - -Learn more about input validation [here](https://trpc.io/docs/router#input-validation).