[Typescript] tRPC: ์†Œ๊ฐœ

[์›๋ณธ ๋งํฌ]

tRPC๋Š” typescript์— ๊ธฐ๋ฐ˜ํ•œ RPC ํ”„๋กœํ† ์ฝœ์ด๋‹ค.
gRPC์˜ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋ฒ„์ „์ด๋ผ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

MSA์—๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , ๋‹จ์ˆœํ•œ ์›น์„œ๋ฒ„<->์›น ํด๋ผ์ด์–ธํŠธ์˜ ๊ตฌ์กฐ๋กœ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
๋‹จ์ˆœํ•˜๊ณ  ํŽธ๋ฆฌํ•œ ํ˜•ํƒœ์—, Typescript ํ™˜๊ฒฝ์— ์ตœ์ ํ™”๋˜์–ด์žˆ๋‹ค๋Š”๊ฒŒ ์žฅ์ ์ด์ง€๋งŒ, ๊ทธ๊ฒŒ ๊ณง ๋‹จ์ ์ด๊ธฐ๋„ ํ•˜๋‹ค.

gRPC๋งˆ์ €๋„ ์–ธ์–ด๋งˆ๋‹ค ๊ตฌํ˜„์ฒด์˜ ๋ฏธํกํ•จ์ด ๊ฝค๋‚˜ ์กด์žฌํ•˜๋Š” ๋งˆ๋‹น์ธ๋ฐ, Typescript์— ์ข…์†๋œ tRPC๋Š” ์ƒํƒœ๊ณ„๊ฐ€ ์–ผ๋งˆ๋‚˜ ์ข‹๊ฒ ๋Š”๊ฐ€? ์‚ฌ์‹ค์ƒ Node.js ํ™˜๊ฒฝ์—์„œ๋งŒ ์ด์ƒ์ ์ธ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
๊ทธ๋ž˜์„œ MSA์˜ ์žฅ์ ์„ ์ด๋Œ์–ด๋‚ด๊ธฐ์—๋Š” ์ œํ•œ์ด ์ข€ ์žˆ๋‹ค.




์„œ๋ฒ„ ์ธก ๊ตฌํ˜„

์„œ๋ฒ„๋ถ€ํ„ฐ ํ•ด๋ณด๊ฒ ๋‹ค.
๋จผ์ € ์„œ๋ฒ„์šฉ ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•œ๋‹ค.

npm i @trpc/server

์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” "์œ ์ € ๋ชฉ๋ก"์ด ์žˆ๊ณ , ์œ ์ €๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.
๋จผ์ € ๊ธฐ๋ณธ tRPC ๊ฐ์ฒด์™€ ๋ผ์šฐํ„ฐ๋ฅผ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.

import { initTRPC } from '@trpc/server';

const trpc = initTRPC.create();

const router = trpc.router;

const userList: User[] = [
    {
        id: '1',
        name: 'John Doe',
    },
    {
        id: '2',
        name: 'Sam Smith',
    },
];

interface User {
    id: string;
    name: string;
}

const appRouter = router({});

// Export type router type signature,
// NOT the router itself.
export type AppRouter = typeof appRouter;

๊ทธ๋ฆฌ ์–ด๋ ค์šธ ๊ฒƒ์€ ์—†๋‹ค.

์ด์ œ ์—ฌ๊ธฐ๋‹ค๊ฐ€ ์œ ์ €๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋Š” userById ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•ด๋ณด๊ฒ ๋‹ค.

import { initTRPC } from '@trpc/server';

const trpc = initTRPC.create();

const router = trpc.router;

const userList: User[] = [
    {
        id: '1',
        name: 'John Doe',
    },
    {
        id: '2',
        name: 'Sam Smith',
    },
];

interface User {
    id: string;
    name: string;
}

interface UserByIdInput {
    id: string;
}

const appRouter = router({
    userById: trpc.procedure
        .input((value: any)=>{
            if(typeof value?.id !== 'string'){
                throw new Error('input.id must be an string');
            } 

            return value as UserByIdInput;
        })
        .query((request)=>{
            const input = request.input;

            return userList.find((user)=>user.id === input.id);
        })
});

// Export type router type signature,
// NOT the router itself.
export type AppRouter = typeof appRouter;

๊ทธ๋ƒฅ ๋ผ์šฐํ„ฐ์— key-value ํ˜•ํƒœ๋กœ ๊ฐ’์„ ์ง‘์–ด๋„ฃ์œผ๋ฉด ๊ฐ๊ฐ์˜ route๊ฐ€ ๋œ๋‹ค.

input ํ•จ์ˆ˜๋Š” ์ž…๋ ฅ ๊ฐ’์„ ์–ด๋–ป๊ฒŒ ๊ฒ€์ฆํ•˜๊ณ  ๊ฐ€๊ณตํ• ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ ,
query๋Š” input์—์„œ ๊ฐ€๊ณตํ•œ ๊ฐ’์„ ๋ฐ›์•„์„œ ์ตœ์ข…์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋Œ๋ ค์ค„ ๊ฐ’์„ ๋ฆฌํ„ดํ•œ๋‹ค.
๊ธฐ๋ณธ์ ์ธ ์›๋ฆฌ๋Š” ์–ด๋ ค์šธ ๊ฒƒ์ด ์—†๋‹ค.

์ด "๋ผ์šฐํ„ฐ" ๋‹จ์œ„๋Š” ์„œ๋ฒ„์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š”๊ฒŒ ์•„๋‹ˆ๋ผ, ์ด ์ž์ฒด๋กœ ํ•˜๋‚˜์˜ ๊ทœ๊ฒฉ์ด ๋œ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ ์—ฐ๋™ํ•  ๊ฒฝ์šฐ์—๋„ ์ € ๋ผ์šฐํ„ฐ ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•˜๋ฉด ๋œ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ €๊ฒƒ๋งŒ์œผ๋กœ๋Š” ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ๋ชปํ•œ๋‹ค. ๊ทธ๋ƒฅ tRPC๋ฅผ ์ •์˜ํ•˜๋Š” ๊ฒƒ์ผ ๋ฟ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
์‹ค์ œ๋กœ ์ €๊ฒŒ ์„œ๋ฒ„๋กœ์„œ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜๋ ค๋ฉด, express ๊ฐ™์€ ๊ธฐ์กด ์„œ๋ฒ„ ํ™˜๊ฒฝ์— ๋ถ™์—ฌ์„œ ์จ์•ผ ํ•œ๋‹ค.

import express from 'express'
import * as trpcExpress from '@trpc/server/adapters/express';

const server = express();

server.get('/', (req, res)=>{
    res.json('OK');
})

server.get('/', (req, res)=>{
    res.json('OK');
})

// created for each request
const createTRPCContext = ({req, res}: trpcExpress.CreateExpressContextOptions) => ({});

server.use(
    '/trpc',
    trpcExpress.createExpressMiddleware({
        router: appRouter,
        createContext: createTRPCContext,
    }),
);

server.listen(12345, ()=>{console.log('Server is Running...')});




ํด๋ผ์ด์–ธํŠธ ์ธก ๊ตฌํ˜„

์ด๋ฒˆ์—๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ์ €๊ฑธ ํ˜ธ์ถœํ•ด๋ณด์ž.
ํด๋ผ์ด์–ธํŠธ์šฉ ๋ชจ๋“ˆ์„ ๋จผ์ € ์„ค์น˜ํ•œ๋‹ค.

npm i @trpc/client

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ์‹์œผ๋กœ client ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ ํ›„, ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

// @filename: client.ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

// Notice the <AppRouter> generic here.
const trpcClient = createTRPCProxyClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:12345/trpc',
    }),
  ],
});

async function main() {
    const user = await trpcClient.userById.query({id: '1'});
    console.log(user);
}

main();

url์—๋Š” ์„œ๋ฒ„๊ฐ€ ๋– ์žˆ๋Š” URL ๊ฒฝ๋กœ๋ฅผ ์ ์–ด์ฃผ๋ฉด ๋˜๊ณ 

API๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์„œ๋ฒ„์—์„œ ์ •์˜ํ–ˆ๋˜๊ฑธ ๊ทธ๋Œ€๋กœ ๊บผ๋‚ด์“ธ ์ˆ˜ ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ.

๊ธฐ๋ณธ์ ์ธ ๊ตฌ์กฐ๋Š” ์ด๋ ‡๊ณ , react ๋“ฑ์˜ ํ”„๋ก ํŠธ์—”๋“œ ํ”„๋ ˆ์ž„์›Œํฌ๋“ค์—์„œ๋„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.



์ฐธ์กฐ
tRPC | tRPC