Compare commits

...

25 Commits

Author SHA1 Message Date
wobsoriano
f061b0531f chore: release v0.4.2 2022-11-17 02:42:20 -08:00
wobsoriano
f08b724df7 feat: get req,res values from event.node 2022-11-17 02:42:01 -08:00
wobsoriano
4085d5fe26 fix: request types 2022-11-17 02:41:01 -08:00
wobsoriano
13713343ce fix: request types 2022-11-17 02:39:45 -08:00
wobsoriano
d6f11bb301 feat(deps): bump h3 to 1.0.1 2022-11-17 02:38:27 -08:00
wobsoriano
c5c1f1cdba feat(deps): bump h3, ohash and ufo to 1.0.0 2022-11-17 02:38:17 -08:00
wobsoriano
0681dd554d Merge branch 'next' of https://github.com/wobsoriano/trpc-nuxt into next 2022-11-16 08:48:14 -08:00
wobsoriano
e7a76694af update docs 2022-11-16 08:48:09 -08:00
wobsoriano
da75c18ebe update local nuxt dep to stable 2022-11-16 08:42:45 -08:00
wobsoriano
6289575900 bump local trpc deps to rc.8 2022-11-16 08:40:35 -08:00
Robert Soriano
ac212672e4 Update 2.server-side-calls.md 2022-11-14 12:26:48 -08:00
Robert Soriano
b8e2a83e26 Merge pull request #42 from nkhdo/patch-1
Update 2.server-side-calls.md
2022-11-14 10:43:06 -08:00
Hoang Do
fd98b0d414 Update 2.server-side-calls.md
Fix wrong variable import
2022-11-15 00:46:06 +07:00
wobsoriano
20ab235856 docs: add auth tips 2022-11-13 11:18:57 -08:00
wobsoriano
7205c356f8 docs: update aborting procs 2022-11-13 11:08:28 -08:00
wobsoriano
e14827e0c7 docs: add aborting procs 2022-11-13 11:05:47 -08:00
wobsoriano
eea6406d5a docs: update example 2022-11-13 10:58:27 -08:00
wobsoriano
2d3409a25b docs: update example 2022-11-13 10:58:08 -08:00
wobsoriano
7a71a6e128 docs: add mutation example 2022-11-13 10:55:23 -08:00
wobsoriano
d8e89297cd docs: typo fix 2022-11-13 10:41:35 -08:00
wobsoriano
6768205318 docs: update composables 2022-11-13 10:41:22 -08:00
wobsoriano
c20a092a31 docs: add tips section 2022-11-13 10:37:24 -08:00
wobsoriano
64df8fb7ad infer error in playground 2022-11-13 09:43:23 -08:00
wobsoriano
d5567e5826 remove character 2022-11-12 21:16:19 -08:00
wobsoriano
6b898e836c update readme 2022-11-12 21:15:19 -08:00
15 changed files with 1110 additions and 1299 deletions

View File

@@ -2,6 +2,17 @@
End-to-end typesafe APIs with [tRPC.io](https://trpc.io/) in Nuxt applications.
<p align="center">
<figure>
<img src="https://i.imgur.com/3AZlBZH.gif" alt="Demo" />
<figcaption>
<p align="center">
The client above is <strong>not</strong> importing any code from the server, only its type declarations.
</p>
</figcaption>
</figure>
</p>
Docs: https://trpc-nuxt.vercel.app
For version 3 of this module (tRPC v9, auto-imports, auto handlers), [go here](https://github.com/wobsoriano/trpc-nuxt/tree/v3).

View File

@@ -5,15 +5,14 @@ export default defineAppConfig({
alt: 'tRPC-Nuxt cover',
url: 'https://trpc-nuxt.vercel.app',
debug: false,
socials: {
github: 'wobsoriano/trpc-nuxt'
},
aside: {
level: 1
},
footer: {
credits: {
icon: 'IconDocus',
text: 'Powered by Docus',
href: 'https://docus.com'
},
credits: true,
icons: [
{
label: 'NuxtJS',

View File

@@ -33,8 +33,6 @@ For making typesafe API calls from your client.
Most examples use [Zod](https://github.com/colinhacks/zod) for input validation and tRPC.io highly recommends it, though it isn't required.
::
## Next Steps
Now that you've installed the required dependencies, you are ready to start building your application.

View File

@@ -0,0 +1,40 @@
---
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.
## 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.

View 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
}
})
```
::

View 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
})
})
}
```

View File

@@ -0,0 +1,104 @@
---
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.
## Create context from request headers
```ts [server/trpc/context.ts]
import type { H3Event } from 'h3'
import { inferAsyncReturnType } from '@trpc/server'
import { decodeAndVerifyJwtToken } from './somewhere/in/your/app/utils'
export async function createContext({
req,
res,
}: 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
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<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.

View File

@@ -0,0 +1,4 @@
---
navigation: false
redirect: /get-started/installation
---

View File

@@ -24,8 +24,8 @@ End-to-end typesafe APIs in Nuxt applications.
#extra
::list
- Automatic typesafety
- Automatic typesafety
- Snappy DX
- Autocompletion
- Autocompletion on the client, for inputs, outputs and errors
::
::

View File

@@ -8,12 +8,10 @@
"preview": "nuxi preview"
},
"dependencies": {
"@vueuse/head": "^1.0.3",
"nuxt": "^3.0.0-rc.13",
"@nuxtjs/tailwindcss": "^6.1.3"
"nuxt": "^3.0.0"
},
"devDependencies": {
"@nuxt-themes/docus": "npm:@nuxt-themes/docus-edge@latest",
"@nuxtlabs/github-module": "npm:@nuxtlabs/github-module-edge@latest"
"@nuxt-themes/docus": "^0.3.1",
"@nuxtlabs/github-module": "^1.5.3"
}
}

View File

@@ -2,7 +2,7 @@
"name": "trpc-nuxt",
"description": "End-to-end typesafe APIs in Nuxt applications.",
"type": "module",
"version": "0.4.1",
"version": "0.4.2",
"license": "MIT",
"sideEffects": false,
"exports": {
@@ -31,17 +31,15 @@
"@trpc/server": "^10.0.0-proxy-beta.21"
},
"dependencies": {
"h3": "^0.8.6",
"h3": "^1.0.1",
"nanoid": "^4.0.0",
"nuxt": "^3.0.0-rc.13",
"ohash": "^0.1.5",
"ufo": "^0.8.6"
"ohash": "^1.0.0",
"ufo": "^1.0.0"
},
"devDependencies": {
"@nuxt/kit": "3.0.0-rc.13",
"@nuxtjs/eslint-config-typescript": "^11.0.0",
"@trpc/client": "10.0.0-rc.7",
"@trpc/server": "10.0.0-rc.7",
"@trpc/client": "10.0.0-rc.8",
"@trpc/server": "10.0.0-rc.8",
"bumpp": "^8.2.1",
"concurrently": "^7.5.0",
"eslint": "^8.25.0",

View File

@@ -9,13 +9,13 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@trpc/client": "10.0.0-rc.7",
"@trpc/server": "10.0.0-rc.7",
"@trpc/client": "10.0.0-rc.8",
"@trpc/server": "10.0.0-rc.8",
"superjson": "^1.11.0",
"trpc-nuxt": "workspace:*",
"zod": "^3.19.1"
},
"devDependencies": {
"nuxt": "^3.0.0-rc.13"
"nuxt": "^3.0.0"
}
}

View File

@@ -1,4 +1,8 @@
<script setup lang="ts">
import { TRPCClientError } from '@trpc/client';
import type { inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from '~~/server/trpc/routers';
const { $client } = useNuxtApp()
// const headers = useClientHeaders()
@@ -23,7 +27,10 @@ const addTodo = async () => {
}
}
const { data: todos, pending, error, refresh } = await useAsyncData(() => $client.todo.getTodos.query())
type RouterOutput = inferRouterOutputs<AppRouter>;
type ErrorOutput = TRPCClientError<AppRouter>
const { data: todos, pending, error, refresh } = await useAsyncData<RouterOutput['todo']['getTodos'], ErrorOutput>(() => $client.todo.getTodos.query())
</script>
<template>

2086
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@ export interface OnErrorPayload<TRouter extends AnyRouter> {
error: TRPCError
type: ProcedureType | 'unknown'
path: string | undefined
req: H3Event['req']
req: H3Event['node']['req']
input: unknown
ctx: undefined | inferRouterContext<TRouter>
}
@@ -68,7 +68,7 @@ export function createNuxtApiHandler<TRouter extends AnyRouter> ({
const {
req,
res
} = event
} = event.node
const $url = createURL(req.url!)