Almost completed development env

This commit is contained in:
2025-05-23 15:30:12 +02:00
parent 16a6eace38
commit 3e39cfe19d
11 changed files with 246 additions and 97 deletions

View File

@@ -21,7 +21,9 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@trpc/server": "^11.1.2",
"dotenv": "^16.5.0",
"fastify": "^5.3.3"
"fastify": "^5.3.3",
"zod": "^3.25.23"
}
}

View File

@@ -0,0 +1,8 @@
import type { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
export function createContext({ req, res }: CreateFastifyContextOptions) {
const user = { name: req.headers.username ?? 'anonymous' };
return { req, res, user };
}
export type Context = Awaited<ReturnType<typeof createContext>>;

View File

@@ -1,42 +1,29 @@
import Fastify from 'fastify';
const fastify = Fastify({
logger: true,
import { fastifyTRPCPlugin, type FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify';
import fastify from 'fastify';
import { createContext } from './context.ts';
import { appRouter, type AppRouter } from './router.ts';
const server = fastify({
maxParamLength: 5000,
});
fastify.route({
method: 'GET',
url: '/',
schema: {
// request needs to have a querystring with a `name` parameter
querystring: {
type: 'object',
properties: {
name: { type: 'string' },
},
required: ['name'],
server.register(fastifyTRPCPlugin, {
prefix: '/api/trpc',
trpcOptions: {
router: appRouter,
createContext,
onError({ path, error }) {
// report to error monitoring
console.error(`Error in tRPC handler on path '${path}':`, error);
},
// the response needs to be an object with an `hello` property of type 'string'
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' },
},
},
},
},
// this function is executed for every request before the handler is executed
preHandler: async (request, reply) => {
// E.g. check authentication
},
handler: async (request, reply) => {
return { hello: 'world' };
},
} satisfies FastifyTRPCPluginOptions<AppRouter>['trpcOptions'],
});
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
(async () => {
try {
await server.listen({ port: 3000 });
} catch (err) {
server.log.error(err);
process.exit(1);
}
})();

View File

@@ -0,0 +1,33 @@
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
type User = {
id: string;
name: string;
bio?: string;
};
const users: Record<string, User> = {};
export const t = initTRPC.create();
export const appRouter = t.router({
demo: t.procedure.query(() => 'test'),
getUserById: t.procedure.input(z.string()).query((opts) => {
return users[opts.input]; // input type is string
}),
createUser: t.procedure
.input(
z.object({
name: z.string().min(3),
bio: z.string().max(142).optional(),
}),
)
.mutation((opts) => {
const id = Date.now().toString();
const user: User = { id, ...opts.input };
users[user.id] = user;
return user;
}),
});
// export type definition of API
export type AppRouter = typeof appRouter;

View File

@@ -14,11 +14,14 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.0.6",
"@tanstack/react-query": "^5.66.5",
"@tanstack/react-query": "^5.76.1",
"@tanstack/react-query-devtools": "^5.66.5",
"@tanstack/react-router": "^1.114.3",
"@tanstack/react-router-devtools": "^1.114.3",
"@tanstack/router-plugin": "^1.114.3",
"@trpc/client": "^11.1.2",
"@trpc/server": "^11.1.2",
"@trpc/tanstack-react-query": "^11.1.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwindcss": "^4.0.6"

View File

@@ -1,6 +1,6 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
export const queryClient = new QueryClient()
export function getContext() {
return {

View File

@@ -12,6 +12,7 @@
import { Route as rootRoute } from './routes/__root'
import { Route as IndexImport } from './routes/index'
import { Route as DemoTrpcImport } from './routes/demo.trpc'
import { Route as DemoTanstackQueryImport } from './routes/demo.tanstack-query'
// Create/Update Routes
@@ -22,6 +23,12 @@ const IndexRoute = IndexImport.update({
getParentRoute: () => rootRoute,
} as any)
const DemoTrpcRoute = DemoTrpcImport.update({
id: '/demo/trpc',
path: '/demo/trpc',
getParentRoute: () => rootRoute,
} as any)
const DemoTanstackQueryRoute = DemoTanstackQueryImport.update({
id: '/demo/tanstack-query',
path: '/demo/tanstack-query',
@@ -46,6 +53,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DemoTanstackQueryImport
parentRoute: typeof rootRoute
}
'/demo/trpc': {
id: '/demo/trpc'
path: '/demo/trpc'
fullPath: '/demo/trpc'
preLoaderRoute: typeof DemoTrpcImport
parentRoute: typeof rootRoute
}
}
}
@@ -54,36 +68,41 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/demo/trpc': typeof DemoTrpcRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/demo/trpc': typeof DemoTrpcRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
'/demo/trpc': typeof DemoTrpcRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/demo/tanstack-query'
fullPaths: '/' | '/demo/tanstack-query' | '/demo/trpc'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/demo/tanstack-query'
id: '__root__' | '/' | '/demo/tanstack-query'
to: '/' | '/demo/tanstack-query' | '/demo/trpc'
id: '__root__' | '/' | '/demo/tanstack-query' | '/demo/trpc'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
DemoTrpcRoute: typeof DemoTrpcRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
DemoTrpcRoute: DemoTrpcRoute,
}
export const routeTree = rootRoute
@@ -97,7 +116,8 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
"/demo/tanstack-query"
"/demo/tanstack-query",
"/demo/trpc"
]
},
"/": {
@@ -105,6 +125,9 @@ export const routeTree = rootRoute
},
"/demo/tanstack-query": {
"filePath": "demo.tanstack-query.tsx"
},
"/demo/trpc": {
"filePath": "demo.trpc.tsx"
}
}
}

View File

@@ -0,0 +1,18 @@
import { createFileRoute } from '@tanstack/react-router'
import { useQuery } from '@tanstack/react-query'
import { trpc } from '@/utils/trpc.ts'
export const Route = createFileRoute('/demo/trpc')({
component: TRPCDemo,
})
function TRPCDemo() {
const { data } = useQuery(trpc.demo.queryOptions())
return (
<div className="p-4">
<h1 className="text-2xl mb-4">People list</h1>
<ul>Demo Output: {data}</ul>
</div>
)
}

View File

@@ -0,0 +1,18 @@
import { createTRPCClient, httpBatchLink } from '@trpc/client'
import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query'
import type { AppRouter } from '../../../backend/src/router.ts'
import { queryClient } from '@/integrations/tanstack-query/root-provider.tsx'
const trpcClient = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: '/api/trpc' })],
})
export const trpc = createTRPCOptionsProxy<AppRouter>({
client: trpcClient,
queryClient,
})
// We are using the singleton pattern because we don't have SSR,
// so to use trpc instead of `useTRPC()` (context-based api) we just
// do `import { trpc } from './utils/trpc'` and directly use the object.
// This is safe BECAUSE THERE IS NO SSR AND THIS IS A FULL CSR WEBAPP

View File

@@ -1,20 +1,36 @@
import { defineConfig } from "vite";
import viteReact from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
import viteReact from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import { resolve } from "node:path";
import { TanStackRouterVite } from '@tanstack/router-plugin/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [TanStackRouterVite({ autoCodeSplitting: true }), viteReact(), tailwindcss()],
plugins: [
TanStackRouterVite({ autoCodeSplitting: true }),
viteReact(),
tailwindcss(),
],
test: {
globals: true,
environment: "jsdom",
environment: 'jsdom',
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
}
});
},
server: {
// On Prod the frontend are just static file served directly by the backend node.js.
// On Dev we connect instead to the Vite server.
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
cookieDomainRewrite: '',
cookiePathRewrite: '/',
},
},
},
})