Experiments with tRPC and Nuxt Server API

This commit is contained in:
2025-04-03 19:24:59 +02:00
parent 4c058b748b
commit b4d22ee561
10 changed files with 233 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
<template>
<div>
<NuxtRouteAnnouncer />
<NuxtWelcome />
</div>
<NuxtRouteAnnouncer/>
<NuxtLoadingIndicator/>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
</template>

View File

@@ -2,5 +2,8 @@
export default defineNuxtConfig({
compatibilityDate: '2024-11-01',
devtools: { enabled: true },
modules: ['@nuxt/eslint']
modules: ['@nuxt/eslint'],
build: {
transpile: ['trpc-nuxt']
},
})

View File

@@ -11,10 +11,21 @@
},
"dependencies": {
"@nuxt/eslint": "1.3.0",
"@trpc/client": "^11.0.1",
"@trpc/server": "^11.0.1",
"eslint": "^9.0.0",
"lowdb": "^7.0.1",
"match-sorter": "^8.0.0",
"nuxt": "^3.16.2",
"sort-by": "^1.2.0",
"tiny-invariant": "^1.3.3",
"trpc-nuxt": "^1.0.4",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"zod": "^3.24.2"
},
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b"
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
"devDependencies": {
"typescript": "^5.8.2"
}
}

27
pages/index.vue Normal file
View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
const { $trpc } = useNuxtApp();
const { data: hello } = await $trpc.hello.useQuery({ text: 'client' });
// Il typing sul risultato e' corretto, ma sul body no
const { data: otherHello } = await useFetch('/api/test/HELLO', {
method: 'post',
body: {
world: 'MONDO'
}
});
</script>
<template>
<div>
<h1>Index Page</h1>
<div>
<p>RPC Client output: {{ hello }}</p>
<p>Nuxt API output: {{ otherHello?.risultato }}</p>
</div>
</div>
</template>
<style scoped>
</style>

14
plugins/trpc.ts Normal file
View File

@@ -0,0 +1,14 @@
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client';
import type { AppRouter } from '~/server/trpc/routers';
export default defineNuxtPlugin(() => {
const trpc = createTRPCNuxtClient<AppRouter>({
links: [httpBatchLink({ url: '/api/trpc' })],
});
return {
provide: {
trpc,
},
};
});

120
pnpm-lock.yaml generated
View File

@@ -11,18 +11,46 @@ importers:
'@nuxt/eslint':
specifier: 1.3.0
version: 1.3.0(@vue/compiler-sfc@3.5.13)(eslint@9.23.0(jiti@2.4.2))(magicast@0.3.5)(typescript@5.8.2)(vite@6.2.5(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.1))
'@trpc/client':
specifier: ^11.0.1
version: 11.0.1(@trpc/server@11.0.1(typescript@5.8.2))(typescript@5.8.2)
'@trpc/server':
specifier: ^11.0.1
version: 11.0.1(typescript@5.8.2)
eslint:
specifier: ^9.0.0
version: 9.23.0(jiti@2.4.2)
lowdb:
specifier: ^7.0.1
version: 7.0.1
match-sorter:
specifier: ^8.0.0
version: 8.0.0
nuxt:
specifier: ^3.16.2
version: 3.16.2(@parcel/watcher@2.5.1)(db0@0.3.1)(eslint@9.23.0(jiti@2.4.2))(ioredis@5.6.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.39.0)(terser@5.39.0)(typescript@5.8.2)(vite@6.2.5(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.1))(yaml@2.7.1)
sort-by:
specifier: ^1.2.0
version: 1.2.0
tiny-invariant:
specifier: ^1.3.3
version: 1.3.3
trpc-nuxt:
specifier: ^1.0.4
version: 1.0.4(@trpc/client@11.0.1(@trpc/server@11.0.1(typescript@5.8.2))(typescript@5.8.2))(@trpc/server@11.0.1(typescript@5.8.2))
vue:
specifier: ^3.5.13
version: 3.5.13(typescript@5.8.2)
vue-router:
specifier: ^4.5.0
version: 4.5.0(vue@3.5.13(typescript@5.8.2))
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
typescript:
specifier: ^5.8.2
version: 5.8.2
packages:
@@ -138,6 +166,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/runtime@7.27.0':
resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.0':
resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==}
engines: {node: '>=6.9.0'}
@@ -920,6 +952,17 @@ packages:
peerDependencies:
eslint: '>=9.0.0'
'@trpc/client@11.0.1':
resolution: {integrity: sha512-HvOrvWAXbGBwt4om+NfuxrK4f+ik2aaNIXq7WLrJbEp7U+YXfh5++1a5p4JDaikrvSaObJ389DhYAGWz90xSGw==}
peerDependencies:
'@trpc/server': 11.0.1
typescript: '>=5.7.2'
'@trpc/server@11.0.1':
resolution: {integrity: sha512-DZS3+bONLno4oJjHBMrS3xRPtoZsnmBImDH/Mmo1QhaFt3Xp/S0Riu7WU/oofsLTVj2m1UO5LlgLJFumvlSNoQ==}
peerDependencies:
typescript: '>=5.7.2'
'@trysound/sax@0.2.0':
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}
@@ -2278,6 +2321,10 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
lowdb@7.0.1:
resolution: {integrity: sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==}
engines: {node: '>=18'}
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -2294,6 +2341,9 @@ packages:
magicast@0.3.5:
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
match-sorter@8.0.0:
resolution: {integrity: sha512-bGJ6Zb+OhzXe+ptP5d80OLVx7AkqfRbtGEh30vNSfjNwllu+hHI+tcbMIT/fbkx/FKN1PmKuDb65+Oofg+XUxw==}
mdn-data@2.0.28:
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
@@ -2478,6 +2528,10 @@ packages:
engines: {node: ^14.16.0 || >=16.10.0}
hasBin: true
object-path@0.6.0:
resolution: {integrity: sha512-fxrwsCFi3/p+LeLOAwo/wyRMODZxdGBtUlWRzsEpsUVrisZbEfZ21arxLGfaWfcnqb8oHPNihIb4XPE8CQPN5A==}
engines: {node: '>=0.8.0'}
ofetch@1.4.1:
resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
@@ -2873,6 +2927,9 @@ packages:
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
regexp-ast-analysis@0.7.1:
resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -2885,6 +2942,9 @@ packages:
resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==}
hasBin: true
remove-accents@0.5.0:
resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==}
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -3012,6 +3072,9 @@ packages:
smob@1.5.0:
resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==}
sort-by@1.2.0:
resolution: {integrity: sha512-aRyW65r3xMnf4nxJRluCg0H/woJpksU1dQxRtXYzau30sNBOmf5HACpDd9MZDhKh7ALQ5FgSOfMPwZEtUmMqcg==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -3059,6 +3122,10 @@ packages:
std-env@3.8.1:
resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==}
steno@4.0.2:
resolution: {integrity: sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==}
engines: {node: '>=18'}
streamx@2.22.0:
resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==}
@@ -3184,6 +3251,12 @@ packages:
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
trpc-nuxt@1.0.4:
resolution: {integrity: sha512-HIIBVGGIxvMWUWrb9FeVd+qvAPLSf47Eqku0zKcwncQI0v/3WddWegGer0DqsMIGbLDYxfmP/NdZMJu+V1PJwA==}
peerDependencies:
'@trpc/client': ^11.0.0
'@trpc/server': ^11.0.0
ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'}
@@ -3576,6 +3649,9 @@ packages:
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
engines: {node: '>= 14'}
zod@3.24.2:
resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==}
snapshots:
'@ampproject/remapping@2.3.0':
@@ -3736,6 +3812,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@babel/runtime@7.27.0':
dependencies:
regenerator-runtime: 0.14.1
'@babel/template@7.27.0':
dependencies:
'@babel/code-frame': 7.26.2
@@ -4590,6 +4670,15 @@ snapshots:
- supports-color
- typescript
'@trpc/client@11.0.1(@trpc/server@11.0.1(typescript@5.8.2))(typescript@5.8.2)':
dependencies:
'@trpc/server': 11.0.1(typescript@5.8.2)
typescript: 5.8.2
'@trpc/server@11.0.1(typescript@5.8.2)':
dependencies:
typescript: 5.8.2
'@trysound/sax@0.2.0': {}
'@tybys/wasm-util@0.9.0':
@@ -6031,6 +6120,10 @@ snapshots:
lodash@4.17.21: {}
lowdb@7.0.1:
dependencies:
steno: 4.0.2
lru-cache@10.4.3: {}
lru-cache@5.1.1:
@@ -6051,6 +6144,11 @@ snapshots:
'@babel/types': 7.27.0
source-map-js: 1.2.1
match-sorter@8.0.0:
dependencies:
'@babel/runtime': 7.27.0
remove-accents: 0.5.0
mdn-data@2.0.28: {}
mdn-data@2.0.30: {}
@@ -6390,6 +6488,8 @@ snapshots:
pkg-types: 2.1.0
tinyexec: 0.3.2
object-path@0.6.0: {}
ofetch@1.4.1:
dependencies:
destr: 2.0.4
@@ -6787,6 +6887,8 @@ snapshots:
dependencies:
'@eslint-community/regexpp': 4.12.1
regenerator-runtime@0.14.1: {}
regexp-ast-analysis@0.7.1:
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -6798,6 +6900,8 @@ snapshots:
dependencies:
jsesc: 3.0.2
remove-accents@0.5.0: {}
require-directory@2.1.1: {}
resolve-from@4.0.0: {}
@@ -6942,6 +7046,10 @@ snapshots:
smob@1.5.0: {}
sort-by@1.2.0:
dependencies:
object-path: 0.6.0
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -6982,6 +7090,8 @@ snapshots:
std-env@3.8.1: {}
steno@4.0.2: {}
streamx@2.22.0:
dependencies:
fast-fifo: 1.3.2
@@ -7115,6 +7225,14 @@ snapshots:
tr46@0.0.3: {}
trpc-nuxt@1.0.4(@trpc/client@11.0.1(@trpc/server@11.0.1(typescript@5.8.2))(typescript@5.8.2))(@trpc/server@11.0.1(typescript@5.8.2)):
dependencies:
'@trpc/client': 11.0.1(@trpc/server@11.0.1(typescript@5.8.2))(typescript@5.8.2)
'@trpc/server': 11.0.1(typescript@5.8.2)
h3: 1.15.1
ofetch: 1.4.1
ohash: 2.0.11
ts-api-utils@2.1.0(typescript@5.8.2):
dependencies:
typescript: 5.8.2
@@ -7488,3 +7606,5 @@ snapshots:
archiver-utils: 5.0.2
compress-commons: 6.0.2
readable-stream: 4.7.0
zod@3.24.2: {}

View File

@@ -0,0 +1,15 @@
import { z } from 'zod';
const pathSchema = z.object({
hello: z.string()
});
const bodySchema = z.object({
world: z.string()
});
export default defineEventHandler(async (event) => {
const pathParams = await getValidatedRouterParams(event, pathSchema.parse);
const body = await readValidatedBody(event, bodySchema.parse);
return { risultato: `${pathParams.hello}-${body.world}` };
});

View File

@@ -0,0 +1,7 @@
import { createTRPCNuxtHandler } from 'trpc-nuxt/server';
import { appRouter } from '~/server/trpc/routers';
export default createTRPCNuxtHandler({
endpoint: '/api/trpc',
router: appRouter,
});

9
server/trpc/init.ts Normal file
View File

@@ -0,0 +1,9 @@
import { initTRPC } from '@trpc/server';
// You can use any variable name you like.
// We use t to keep things simple.
const t = initTRPC.create();
export const router = t.router; // createTRPCRouter
export const createCallerFactory = t.createCallerFactory;
export const publicProcedure = t.procedure; // baseProcedure

View File

@@ -0,0 +1,19 @@
import { publicProcedure, router } from '~/server/trpc/init';
import { z } from 'zod';
export const appRouter = router({
hello: publicProcedure
.input(
z.object({
text: z.string(),
}),
)
.query((opts) => {
return {
greeting: `hello ${opts.input.text}`,
};
}),
});
// export type definition of API
export type AppRouter = typeof appRouter;