https://www.youtube.com/watch?v=ED2H_y6dmC8
05:44:04
Update Workflow schema → 워크플로 스키마 업데이트
Create Workflows API → 워크플로 CRUD API 생성
1 - Workflows CRUD API 구현
1-1. User 모델에 Workflow 관계 추가
prisma/schema.prisma
- One-to-Many 관계: 한 사용자는 여러 개의 워크플로우를 가질 수 있다.
- Cascade Delete: 사용자가 삭제되면 해당 사용자의 모든 워크플로우도 함께 삭제
model User {
...
workflows Workflow[]
...
}
model Workflow{
id String @id @default(cuid())
name String
// 기본 구조에 타임스탬프와 관계 필드를 추가
// 타임스탬프
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// user 관계
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
1-2 마이그레이션 실행
npx prisma migrate dev
? Enter a name for the new migration: › workflows-update
2 - 워크플로우 라우터 생성
2-1 워크플로우 이름을 자동으로 생성하기 위한 패키지를 설치
npm i random-word-slugs
2-2. 기본 Create API 생성
src/app/features/workflows/server/routers.ts
- protectedProcedure: 인증된 사용자만 접근 가능
- generateSlug(3): "happy-brown-fox" 형태의 이름 자동 생성
- ctx.auth.user.id: 현재 로그인한 사용자의 ID 사용
import {generateSlug} from "random-word-slugs"
import prisma from "@/lib/db";
import { createTRPCRouter, protectedProcedure } from "@/trpc/init";
export const workflowsRouter = createTRPCRouter({
create: protectedProcedure.mutation(({ctx}) => {
return prisma.workflow.create({
data : {
name: generateSlug(3), // 3개 단어 조합으로 이름 생성
userId: ctx.auth.user.id
}
})
})
})
3 - tRPC 라우터 통합
3-1. App Router 리팩토링
src/trpc/routers/_app.ts 수정
- 기능별로 라우터 분리 (모듈화)
- 코드 가독성 향상
- 유지보수 용이
- 확장성 개선
// 변경전
import { inngest } from '@/inngest/client';
import { createTRPCRouter, premiumProcedure, protectedProcedure } from '../init';
import prisma from '@/lib/db';
import { TRPCError } from '@trpc/server';
import { workflowsRouter } from '@/app/features/workflows/server/routers';
export const appRouter = createTRPCRouter({
testAi : premiumProcedure.mutation(async () => {
await inngest.send({
name: "execute/ai",
})
return {success: true, message: "작업 대기 중"}
}),
getWorkflows: protectedProcedure.query(({ctx}) => {
return prisma.workflow.findMany()
}),
createWorkflow : protectedProcedure.mutation(async()=>{
await inngest.send({
name: "test/hello.world",
data: {
email:"zzuny@zzuny.com"
}
});
return {success: true, message: "작업 대기 중"}
})
});
// export type definition of API
export type AppRouter = typeof appRouter;
// 변경 후
import { createTRPCRouter} from '../init';
import { workflowsRouter } from '@/app/features/workflows/server/routers';
export const appRouter = createTRPCRouter({
workflows : workflowsRouter
});
// export type definition of API
export type AppRouter = typeof appRouter;
4 - 완전한 CRUD API 구현
src/app/features/workflows/server/routers.ts
- 모든 작업에 userId 체크 포함
- 다른 사용자의 워크플로우에 접근 불가
- Zod로 입력값 검증 (name은 최소 1글자 이상)
import z from 'zod'
import {generateSlug} from "random-word-slugs"
import prisma from "@/lib/db";
import { createTRPCRouter, protectedProcedure } from "@/trpc/init";
export const workflowsRouter = createTRPCRouter({
create: protectedProcedure.mutation(({ctx}) => {
return prisma.workflow.create({
data : {
// name: "TODO",
name: generateSlug(3),
userId: ctx.auth.user.id
}
})
}),
remove: protectedProcedure
.input(z.object({id: z.string()}))
.mutation(({ctx, input}) => {
return prisma.workflow.delete({
where: {
id: input.id,
userId: ctx.auth.user.id
}
})
}),
updateName : protectedProcedure
.input(z.object({id: z.string(), name: z.string().min(1)}))
.mutation(({ctx, input}) => {
return prisma.workflow.update({
where: {id: input.id, userId: ctx.auth.user.id},
data: {name: input.name}
})
}),
getOne : protectedProcedure
.input(z.object({id: z.string()}))
.query(({ctx, input}) => {
return prisma.workflow.findUnique({
where: { id: input.id, userId: ctx.auth.user.id}
})
}),
getMany : protectedProcedure
.query(({ctx}) => {
return prisma.workflow.findMany({
where: { userId: ctx.auth.user.id}
})
})
})
5 - React Query Hook 생성
src/app/features/workflows/hooks/use-workflows.ts
* useSuspenseQuery의 장점:
- 데이터가 로드될 때까지 자동으로 Suspense 트리거
- 로딩 상태를 컴포넌트에서 직접 처리할 필요 없음
- React 18의 Concurrent Features 활용
import { useTRPC } from "@/trpc/client"
import { useSuspenseQuery } from "@tanstack/react-query";
/**
Hook to fetch all workflows using suspense
**/
export const useSuspenseWorkflows = () => {
const trpc = useTRPC();
return useSuspenseQuery(trpc.workflows.getMany.queryOptions());
}
6 - 클라이언트 컴포넌트 생성
src/app/features/workflows/components/workflows.tsx
- 현재는 데이터를 JSON으로 출력하지만, 실제 프로젝트에서는 UI 컴포넌트로 렌더링
'use client'
import { useSuspenseWorkflows } from "../hooks/use-workflows"
export const WorkflowsList = () => {
const workflows = useSuspenseWorkflows();
return(
<p>
{JSON.stringify(workflows.data, null, 2)}
</p>
)
}
7 - 서버 사이드 Prefetch 설정
https://trpc.io/docs/client/tanstack-react-query/server-components
Set up with React Server Components | tRPC
This guide is an overview of how one may use tRPC with a React Server Components (RSC) framework such as Next.js App Router.
trpc.io

7-1. tRPC Server Utils 설정
src/trpc/server.tsx
- prefetch와 HydrateClient를 추가
- cache(): React의 캐싱 메커니즘으로 요청당 하나의 Query Client 보장
- prefetch(): 서버에서 데이터를 미리 가져옴
- HydrateClient: 서버에서 가져온 데이터를 클라이언트로 전달
import 'server-only'; // <-- ensure this file cannot be imported from the client
import { createTRPCOptionsProxy, TRPCQueryOptions } from '@trpc/tanstack-react-query';
import { cache } from 'react';
import { createTRPCContext } from './init';
import { makeQueryClient } from './query-client';
import { appRouter } from './routers/_app';
import { dehydrate, HydrationBoundary } from '@tanstack/react-query';
// IMPORTANT: Create a stable getter for the query client that
// will return the same client during the same request.
export const getQueryClient = cache(makeQueryClient);
export const caller = appRouter.createCaller(createTRPCContext);
export const trpc = createTRPCOptionsProxy({
ctx: createTRPCContext,
router: appRouter,
queryClient: getQueryClient,
});
export function prefetch<T extends ReturnType<TRPCQueryOptions<any>>>(
queryOptions: T,
) {
const queryClient = getQueryClient();
if (queryOptions.queryKey[1]?.type === 'infinite') {
void queryClient.prefetchInfiniteQuery(queryOptions as any);
} else {
void queryClient.prefetchQuery(queryOptions);
}
}
export function HydrateClient(props: { children: React.ReactNode }) {
const queryClient = getQueryClient();
return (
<HydrationBoundary state={dehydrate(queryClient)}>
{props.children}
</HydrationBoundary>
);
}
7-2. Prefetch 헬퍼 함수 생성
src/app/features/workflows/server/prefetch.ts
- inferInput으로 입력 타입 자동 추론
- TypeScript로 안전한 파라미터 전달 보장
import { prefetch, trpc } from "@/trpc/server";
import { inferInput } from "@trpc/tanstack-react-query";
type Input = inferInput<typeof trpc.workflows.getMany>
/*
prefetch all workflows
*/
export const prefetchWorkflows = (params: Input) => {
return prefetch(trpc.workflows.getMany.queryOptions(params))
}
8 - Error Boundary 설정
8-1. 패키지 설치
react-error-boundary: React의 에러 경계를 쉽게 구현할 수 있는 라이브러리
npm i react-error-boundary
9 - 페이지 구성
src/app/(dashboard)/(rest)/workflows/page.tsx
- requireAuth(): 비인증 사용자 리다이렉트
- prefetchWorkflows(): 서버에서 데이터를 미리 가져옴
- HydrateClient: 서버 데이터를 클라이언트로 전달
- ErrorBoundary: 에러 발생 시 폴백 UI 표시
- Suspense: 로딩 중 폴백 UI 표시
- WorkflowsList: 실제 워크플로우 목록 렌더링
import { WorkflowsList } from "@/app/features/workflows/components/workflows";
import { prefetchWorkflows } from "@/app/features/workflows/server/prefetch";
import { requireAuth } from "@/lib/auth-utils";
import { HydrateClient } from "@/trpc/server";
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
const Page = async() => {
await requireAuth()
prefetchWorkflows()
return(
<HydrateClient>
<ErrorBoundary fallback={<p>Error!</p>}>
<Suspense fallback={<p>Loading...</p>}>
<WorkflowsList/>
</Suspense>
</ErrorBoundary>
</HydrateClient>
)
}
export default Page;
┌─────────────────────────────────────────────────┐
│ 1. Server Component (Page) │
│ - requireAuth() 인증 확인 │
│ - prefetchWorkflows() 데이터 미리 가져오기 │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 2. HydrateClient │
│ - 서버 데이터를 클라이언트로 전달 │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 3. ErrorBoundary │
│ - 에러 발생 시 폴백 UI 표시 │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 4. Suspense │
│ - 로딩 중 폴백 UI 표시 │
└────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ 5. WorkflowsList (Client Component) │
│ - useSuspenseWorkflows() Hook 사용 │
│ - 캐시된 데이터로 즉시 렌더링 │
└─────────────────────────────────────────────────┘
---- 이어서 WorkflowsList UI 컴포넌트 구현 할거임
'Clone Coding' 카테고리의 다른 글
| N8N & Zapier - Workflows Pagination (0) | 2026.01.15 |
|---|---|
| N8N & Zapier - Workflows Crud / 2 (0) | 2026.01.13 |
| N8N & Zapier - Payments Setup (1) | 2026.01.09 |
| N8N & Zapier - Sidebar Layout (0) | 2026.01.07 |
| N8N & Zapier - Error Tracking (0) | 2026.01.06 |