RedwoodJS로 풀스택 웹 앱 만들기
풀스택 웹 애플리케이션을 처음부터 끝까지 만들려면 생각보다 결정해야 할 것이 많죠. 프론트엔드 프레임워크는 뭘 쓸지, API는 REST로 할지 GraphQL로 할지, 데이터베이스는 어떻게 연결할지, 인증은 어떤 방식으로 구현할지… 이런 고민을 하다 보면 정작 비즈니스 로직을 작성하기도 전에 지쳐버리는 경우가 많은데요.
RedwoodJS는 이런 문제를 해결하려고 만들어진 풀스택 자바스크립트 프레임워크입니다. GitHub의 공동 창업자인 Tom Preston-Werner가 만들었는데, Jekyll이나 Semantic Versioning을 만든 그분이에요. “스타트업이 제품 출시 속도를 높일 수 있도록” 한다는 철학 아래 React와 GraphQL, Prisma 같은 검증된 기술을 하나의 프레임워크로 통합했습니다.
이번 글에서는 RedwoodJS가 어떤 프레임워크인지 살펴보고, 실제로 프로젝트를 생성해서 풀스택 앱을 만드는 과정을 함께 따라가 보겠습니다.
RedwoodJS란?
RedwoodJS는 React 기반의 풀스택 웹 애플리케이션 프레임워크예요. 메타 프레임워크의 일종이라고 볼 수 있지만 Next.js나 Remix와는 접근 방식이 꽤 다릅니다.
Next.js가 렌더링 전략(SSR, SSG, ISR)에 집중한다면 RedwoodJS는 프론트엔드부터 백엔드, 데이터베이스까지 전체 스택을 하나의 프레임워크 안에서 관리하는 데 초점을 맞추고 있어요. 기본 기술 스택을 정리하면 이런 구조입니다.
- React — 프론트엔드 UI
- GraphQL — API 계층 (Apollo Server 기반)
- Prisma — 데이터베이스 ORM (Prisma 소개)
- Jest — 테스트
- Storybook — 컴포넌트 카탈로그
이 모든 게 하나의 모노레포 안에 들어있고 CLI 도구가 보일러플레이트 코드를 자동으로 생성해주니까 개발자는 비즈니스 로직에만 집중할 수 있어요.
프로젝트 생성
RedwoodJS 프로젝트를 만들려면 create-redwood-app CLI를 사용합니다.
yarn create redwood-app my-app
프로젝트 생성이 완료되면 다음과 같은 구조의 디렉토리가 만들어집니다.
my-app/
├── api/ # 백엔드 (GraphQL API + Prisma)
│ ├── db/
│ │ └── schema.prisma
│ └── src/
│ ├── graphql/ # SDL (스키마 정의)
│ ├── services/ # 비즈니스 로직
│ ├── directives/ # 접근 제어
│ ├── functions/ # 서버리스 함수
│ └── lib/
│ ├── auth.js
│ ├── db.js # Prisma 클라이언트
│ └── logger.js
├── web/ # 프론트엔드 (React)
│ ├── src/
│ │ ├── components/
│ │ ├── layouts/
│ │ ├── pages/
│ │ └── Routes.js
│ └── public/
├── scripts/ # 유틸리티 스크립트
├── redwood.toml # 프로젝트 설정
└── package.json
여기서 눈에 띄는 점은 api/와 web/이 완전히 분리되어 있다는 건데요.
RedwoodJS에서는 이것을 각각 “API side”와 “Web side”라고 부릅니다.
두 영역이 같은 저장소에 있지만 독립적으로 동작하며, GraphQL을 통해 통신하는 구조입니다.
개발 서버를 시작하려면 프로젝트 루트에서 다음 명령어를 실행합니다.
yarn redwood dev
그러면 프론트엔드(기본 포트 8910)와 백엔드(기본 포트 8911) 서버가 동시에 뜹니다.
데이터 모델 정의
블로그 앱을 예로 들어 설명해볼게요.
먼저 api/db/schema.prisma 파일에서 데이터 모델을 정의합니다.
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
binaryTargets = "native"
}
model Post {
id Int @id @default(autoincrement())
title String
body String
createdAt DateTime @default(now())
}
Prisma 스키마 문법이 직관적이라서 금방 이해되실 거예요.
Post 모델에 id, title, body, createdAt 필드를 정의했습니다.
스키마를 정의한 후 마이그레이션을 실행합니다.
yarn redwood prisma migrate dev
이 명령어가 데이터베이스 테이블을 생성하고, Prisma 클라이언트 코드도 자동으로 만들어줍니다.
Generator로 CRUD 코드 생성
RedwoodJS에서 가장 강력한 기능 중 하나가 바로 Generator예요. Rails의 scaffold에서 영감을 받았는데, CLI 명령어 하나로 CRUD에 필요한 모든 파일을 자동으로 만들어줘요.
yarn redwood generate scaffold post
이 한 줄로 다음 파일이 생성됩니다.
프론트엔드 쪽에서는 목록 페이지, 상세 페이지, 생성 폼, 수정 폼이 만들어지고 라우트도 자동으로 등록됩니다. 백엔드 쪽에서는 GraphQL 스키마(SDL)와 서비스 파일이 생성되어 CRUD 쿼리와 뮤테이션이 바로 동작합니다.
브라우저에서 http://localhost:8910/posts로 접속하면 게시글을 생성하고 조회하고 수정하고 삭제하는 전체 CRUD 기능이 작동하는 걸 확인할 수 있어요.
데이터베이스 스키마 하나 정의하고 명령어 하나 실행했을 뿐인데 풀스택 CRUD 앱이 완성된 거죠.
Cell: 선언적 데이터 페칭
RedwoodJS에서 가장 독특하고 중요한 개념이 바로 Cell이에요. Cell은 데이터를 가져오는 과정에서 발생하는 여러 상태(로딩 중, 에러, 빈 데이터, 성공)를 선언적으로 처리하는 컴포넌트 패턴인데요.
일반적인 React 앱에서 API를 호출하려면 useEffect와 useState를 조합하거나 TanStack Query 같은 라이브러리를 사용해야 합니다.
RedwoodJS Cell은 이런 데이터 페칭 로직을 프레임워크 차원에서 알아서 처리해줍니다.
yarn redwood generate cell BlogPosts
이 명령어로 생성되는 Cell 파일의 구조를 살펴볼까요?
export const QUERY = gql`
query BlogPostsQuery {
posts {
id
title
body
createdAt
}
}
`;
export const Loading = () => <div>로딩 중...</div>;
export const Empty = () => <div>아직 게시글이 없습니다.</div>;
export const Failure = ({ error }) => (
<div>에러가 발생했습니다: {error.message}</div>
);
export const Success = ({ posts }) => {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
);
};
Cell에는 네 가지 상태에 대응하는 컴포넌트를 export합니다.
QUERY에 GraphQL 쿼리를 정의하면 RedwoodJS가 자동으로 실행하고, 응답 상태에 따라 Loading, Empty, Failure, Success 중 적절한 컴포넌트를 렌더링합니다.
이게 왜 좋냐면 데이터 페칭과 관련된 모든 상태 처리가 하나의 파일에 모이기 때문이에요. 로딩 스피너 깜빡했다거나, 에러 처리를 빼먹었다거나 하는 실수가 구조적으로 줄어들어요. 각 Cell이 자체적으로 데이터를 가져오기 때문에 상위 컴포넌트에서 prop을 전달할 필요가 없어서 prop drilling 문제도 자연스럽게 해결됩니다.
Service: 백엔드 비즈니스 로직
Service는 백엔드의 비즈니스 로직을 담당하는 계층이에요. GraphQL 스키마(SDL)에서 정의한 쿼리와 뮤테이션이 실제로 어떤 동작을 하는지 Service에서 구현합니다.
import { db } from "src/lib/db";
export const posts = () => {
return db.post.findMany();
};
export const post = ({ id }) => {
return db.post.findUnique({ where: { id } });
};
export const createPost = ({ input }) => {
return db.post.create({ data: input });
};
export const updatePost = ({ id, input }) => {
return db.post.update({ data: input, where: { id } });
};
export const deletePost = ({ id }) => {
return db.post.delete({ where: { id } });
};
각 함수가 Prisma 클라이언트를 사용해서 데이터베이스에 접근하는 것이 보이시죠? Service 함수의 이름은 SDL에서 정의한 쿼리/뮤테이션 이름과 자동으로 매핑됩니다.
SDL 파일은 이런 모습입니다.
export const schema = gql`
type Post {
id: Int!
title: String!
body: String!
createdAt: DateTime!
}
type Query {
posts: [Post!]! @requireAuth
post(id: Int!): Post @requireAuth
}
input CreatePostInput {
title: String!
body: String!
}
input UpdatePostInput {
title: String
body: String
}
type Mutation {
createPost(input: CreatePostInput!): Post! @requireAuth
updatePost(id: Int!, input: UpdatePostInput!): Post! @requireAuth
deletePost(id: Int!): Post! @requireAuth
}
`
@requireAuth 디렉티브가 눈에 띄는데요, RedwoodJS는 GraphQL 디렉티브를 통해 인증/인가를 스키마 수준에서 관리합니다.
인증이 필요 없는 공개 API라면 @skipAuth로 바꿔주면 됩니다.
라우팅
RedwoodJS의 라우팅은 파일 시스템 기반이 아니라 하나의 Routes.js 파일에서 명시적으로 관리합니다.
import { Router, Route, Set } from "@redwoodjs/router";
import BlogLayout from "src/layouts/BlogLayout";
const Routes = () => {
return (
<Router>
<Set wrap={BlogLayout}>
<Route path="/" page={HomePage} name="home" />
<Route path="/posts" page={PostsPage} name="posts" />
<Route path="/post/{id:Int}" page={PostPage} name="post" />
<Route path="/post/new" page={NewPostPage} name="newPost" />
<Route path="/post/{id:Int}/edit" page={EditPostPage} name="editPost" />
</Set>
<Route notfound page={NotFoundPage} />
</Router>
);
};
export default Routes;
라우트에 name을 지정하면 코드에서 경로를 하드코딩하지 않고 이름으로 참조할 수 있습니다.
import { Link, routes } from "@redwoodjs/router";
// "/post/42" 경로로 이동하는 링크 생성
<Link to={routes.post({ id: 42 })}>게시글 보기</Link>;
경로가 바뀌어도 name만 유지하면 앱 전체에서 링크가 깨지지 않으니 유지보수할 때 편해요.
{id:Int} 같은 타입 지정도 가능해서 URL 파라미터가 자동으로 타입 변환됩니다.
인증과 배포
인증도 프레임워크에 내장되어 있어요. CLI로 인증 프로바이더를 설정할 수 있습니다.
yarn redwood setup auth dbAuth
dbAuth는 RedwoodJS에 내장된 이메일/비밀번호 기반 인증입니다. 이 외에도 Supabase, Clerk, Auth0, Firebase, Netlify Identity 같은 서드파티 인증을 지원합니다. 인증 설정을 실행하면 로그인, 회원가입, 비밀번호 재설정에 필요한 파일이 자동으로 생성됩니다.
배포도 여러 플랫폼을 지원하는데요.
yarn redwood setup deploy netlify # Netlify
yarn redwood setup deploy vercel # Vercel
yarn redwood setup deploy aws # AWS Lambda
각 명령어가 해당 플랫폼에 맞는 설정 파일과 빌드 스크립트를 자동으로 구성해줍니다.
테스트 지원
테스트 환경도 기본으로 갖추고 있어요. Generator로 파일을 생성하면 테스트 파일도 함께 만들어지는데요.
yarn redwood test
이 명령어로 Jest 기반의 테스트를 실행할 수 있고, 프론트엔드 컴포넌트 테스트에는 React Testing Library가 기본으로 설정되어 있습니다. Storybook도 통합되어 있어서 컴포넌트를 독립적으로 개발하고 문서화하기에도 편리합니다.
yarn redwood storybook
Next.js, Remix와 비교
같은 React 기반 메타 프레임워크지만 철학이 꽤 다릅니다.
Next.js는 렌더링 전략의 유연성에 강점이 있어요. SSR, SSG, ISR, App Router 등 다양한 방식으로 페이지를 렌더링할 수 있고, API 구현도 Route Handler로 자유롭게 할 수 있습니다. 대신 데이터베이스 연결이나 인증은 직접 구성해야 합니다.
Remix는 웹 표준에 가까운 접근 방식을 추구합니다. 중첩 라우팅과 loader/action 패턴으로 서버 사이드 로직을 처리하며, Form 기반의 데이터 처리가 특징적입니다. 역시 데이터베이스나 인증은 개발자가 직접 선택하고 연결해야 해요.
RedwoodJS는 이 두 프레임워크와 달리 “전체 스택을 우리가 결정해줄게”라는 입장입니다. React, GraphQL, Prisma라는 기술 스택이 이미 정해져 있고 그 위에서 Generator가 보일러플레이트 코드를 생성해주니까 고민할 게 훨씬 줄어들어요. 대신 기술 스택을 바꾸기 어렵다는 점은 트레이드오프로 받아들여야 합니다.
정리하면 이렇습니다. 빠른 프로토타이핑이 필요하고 팀이 기술 선택에 시간을 쓰고 싶지 않다면 RedwoodJS가 좋은 선택이에요. 렌더링 전략의 세밀한 제어가 필요하다면 Next.js가, 웹 표준에 가까운 패턴을 선호한다면 Remix가 더 적합할 수 있습니다.
RedwoodJS의 미래: React Server Components
RedwoodJS 팀은 2023년부터 React Server Components(RSC) 도입을 본격적으로 추진해왔습니다. “Bighorn” 에포크라고 불리는 이 전환은 RedwoodJS의 아키텍처를 상당히 바꿔놓을 예정인데요.
기존에는 프론트엔드가 전적으로 클라이언트에서 렌더링되고, GraphQL로 데이터를 가져왔다면, RSC를 도입하면 서버에서 컴포넌트를 렌더링하면서 데이터베이스에 직접 접근하는 것이 가능해집니다. Server Actions를 통한 폼 처리도 지원하여, GraphQL을 거치지 않고도 서버 로직을 실행할 수 있게 됩니다.
이런 변화의 연장선에서 RedwoodSDK라는 새로운 프로젝트가 2025년 3월 v1.0으로 정식 출시되었습니다. Cloudflare 플랫폼에 최적화된 React 프레임워크로, 웹 표준(Request/Response)에 기반한 더 가벼운 접근 방식을 제시하고 있습니다.
마치며
RedwoodJS는 풀스택 웹 개발에서 겪는 “선택 피로”를 줄여주는 프레임워크예요. React, GraphQL, Prisma라는 검증된 기술 스택을 하나로 묶고, Generator와 Cell 같은 독자적인 패턴으로 생산성을 높여줍니다.
특히 스타트업처럼 빠르게 제품을 만들어야 하는 환경에서 빛을 발하는데요. 데이터 모델만 정의하면 CLI 한 줄로 CRUD 앱이 나오고, Cell 패턴으로 프론트엔드 데이터 관리도 깔끔하게 처리할 수 있으니까요.
물론 GraphQL과 Prisma라는 기술 스택이 고정되어 있어서 다른 선택지를 원하는 팀에게는 제약이 될 수 있습니다. 하지만 이 기술들에 관심이 있거나 이미 사용하고 있다면, RedwoodJS가 풀스택 개발 경험을 한 단계 끌어올려줄 거예요.
This work is licensed under
CC BY 4.0