본문으로 건너뛰기

서버 라우트

서버 라우트는 애플리케이션에 서버 측 엔드포인트를 생성할 수 있게 해주는 TanStack Start의 강력한 기능으로, 원시 HTTP 요청, 양식 제출, 사용자 인증 등을 처리하는 데 유용합니다.

서버 라우트는 프로젝트의 ./src/routes 디렉터리에서 TanStack Router 라우트 바로 옆에 정의할 수 있으며, TanStack Start 서버에서 자동으로 처리됩니다.

간단한 서버 라우트는 다음과 같습니다:

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
GET: async ({ request }) => {
return new Response('Hello, World!')
},
},
},
})

[!NOTE] 서버 라우트는 TanStack Start 애플리케이션 외부에서 호출해야 하는 HTTP 엔드포인트를 위한 것입니다. Start 앱 내부에서만 서버 측 로직을 호출하면 되고 직렬화를 Start에서 처리하도록 하려면, 대신 서버 함수를 사용하세요.

서버 라우트와 앱 라우트

서버 라우트는 앱 라우트와 같은 디렉터리에 정의할 수 있으므로, 두 라우트에 동일한 파일을 사용할 수도 있습니다!

// routes/hello.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
POST: async ({ request }) => {
const body = await request.json()
return new Response(JSON.stringify({ message: `Hello, ${body.name}!` }))
},
},
},
component: HelloComponent,
})

function HelloComponent() {
const [reply, setReply] = useState('')

return (
<div>
<button
onClick={() => {
fetch('/hello', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Tanner' }),
})
.then((res) => res.json())
.then((data) => setReply(data.message))
}}
>
Say Hello
</button>
</div>
)
}

파일 라우트 규칙

TanStack Start의 서버 라우트는 TanStack Router와 동일한 파일 기반 라우팅 규칙을 따릅니다. 즉, routes 디렉터리에서 createFileRoute 호출에 server 속성이 있는 각 파일은 API 라우트로 처리됩니다. 몇 가지 예시는 다음과 같습니다:

  • /routes/users.ts/users에 API 라우트를 생성합니다.
  • /routes/users.index.ts/users에 API 라우트를 생성합니다(단, 중복 메서드가 정의되면 오류가 발생합니다).
  • /routes/users/$id.ts/users/$id에 API 라우트를 생성합니다.
  • /routes/users/$id/posts.ts/users/$id/posts에 API 라우트를 생성합니다.
  • /routes/users.$id.posts.ts/users/$id/posts에 API 라우트를 생성합니다
  • /routes/api/file/$.ts/api/file/$에 API 라우트를 생성합니다
  • /routes/my-script[.]js.ts/my-script.js에 API 라우트를 생성합니다

고유한 라우트 경로

각 라우트에는 단 하나의 핸들러 파일만 연결할 수 있습니다. 따라서 요청 경로 /users에 해당하는 routes/users.ts이라는 파일이 있으면, 같은 라우트로 해석되는 다른 파일을 둘 수 없습니다. 예를 들어 다음 파일은 모두 같은 라우트로 해석되므로 오류가 발생합니다:

  • /routes/users.index.ts
  • /routes/users.ts
  • /routes/users/index.ts

이스케이프 문자 매칭

일반 라우트와 마찬가지로 서버 라우트도 이스케이프된 문자와 매칭할 수 있습니다. 예를 들어 routes/users[.]json.ts이라는 파일은 /users.json에 API 라우트를 생성합니다.

경로 없는 레이아웃 라우트와 이탈 라우트

통합 라우팅 시스템 덕분에 서버 라우트 미들웨어와 관련된 유사한 기능을 위해 경로 없는 레이아웃 라우트와 이탈 라우트를 지원합니다.

  • 경로 없는 레이아웃 라우트를 사용하여 라우트 그룹에 미들웨어를 추가할 수 있습니다
  • 이탈 라우트를 사용하여 상위 미들웨어에서 "이탈"할 수 있습니다

중첩 디렉터리와 파일 이름

위 예시에서 파일 명명 규칙이 유연하며 디렉터리와 파일 이름을 자유롭게 조합할 수 있다는 점을 확인했을 수 있습니다. 이는 의도된 것으로, 애플리케이션에 적합한 방식으로 서버 라우트를 구성할 수 있게 합니다. 자세한 내용은 TanStack Router 파일 기반 라우팅 가이드에서 확인할 수 있습니다.

서버 라우트 요청 처리

서버 라우트 요청은 기본적으로 Start가 자동으로 처리하거나, 사용자 지정 src/server.ts 진입점 파일에서 Start의 createStartHandler가 처리합니다.

Start 핸들러는 수신 요청을 서버 라우트와 매칭하고 적절한 미들웨어와 핸들러를 실행합니다.

서버 핸들러를 사용자 지정해야 하는 경우, 사용자 지정 핸들러를 만든 다음 이벤트를 Start 핸들러에 전달하면 됩니다. 서버 진입점을 참조하세요.

서버 라우트 정의

createFileRoute 호출에 server 속성을 추가하여 서버 라우트를 생성합니다. server 속성에는 다음이 포함됩니다:

  • handlers - HTTP 메서드를 핸들러 함수에 매핑하는 객체 또는 고급 사용 사례를 위해 createHandlers을 받는 함수입니다
  • middleware - 모든 핸들러에 적용되는 선택적 라우트 수준 미들웨어 배열입니다
// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
GET: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
},
},
})

서버 라우트 핸들러 정의

핸들러는 두 가지 방식으로 정의할 수 있습니다:

  • 단순 핸들러: handlers 객체에 핸들러 함수를 직접 제공합니다
  • 미들웨어가 있는 핸들러: 미들웨어가 있는 핸들러를 정의하려면 createHandlers 함수를 사용합니다

단순 핸들러

단순한 사용 사례에서는 handlers 객체에 핸들러 함수를 직접 제공할 수 있습니다.

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
GET: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
},
},
})

특정 핸들러에 미들웨어 추가하기

더 복잡한 사용 사례에서는 특정 핸들러에 미들웨어를 추가할 수 있습니다. 이렇게 하려면 createHandlers 함수를 사용해야 합니다:

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: ({ createHandlers }) =>
createHandlers({
GET: {
middleware: [loggerMiddleware],
handler: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
},
}),
},
})

모든 핸들러에 미들웨어 추가하기

서버 수준에서 middleware 속성을 사용하여 라우트의 모든 핸들러에 적용되는 미들웨어를 추가할 수도 있습니다:

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
middleware: [authMiddleware, loggerMiddleware], // Applies to all handlers
handlers: {
GET: async ({ request }) => {
return new Response('Hello, World! from ' + request.url)
},
POST: async ({ request }) => {
const body = await request.json()
return new Response(`Hello, ${body.name}!`)
},
},
},
})

라우트 수준 미들웨어와 핸들러별 미들웨어 결합하기

두 접근 방식을 결합할 수 있습니다. 라우트 수준 미들웨어가 먼저 실행된 후 핸들러별 미들웨어가 실행됩니다:

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
middleware: [authMiddleware], // Runs first for all handlers
handlers: ({ createHandlers }) =>
createHandlers({
GET: async ({ request }) => {
return new Response('Hello, World!')
},
POST: {
middleware: [validationMiddleware], // Runs after authMiddleware, only for POST
handler: async ({ request }) => {
const body = await request.json()
return new Response(`Hello, ${body.name}!`)
},
},
}),
},
})

핸들러 컨텍스트

각 HTTP 메서드 핸들러는 다음 속성이 포함된 객체를 전달받습니다:

  • request: 들어오는 요청 객체입니다. Request 객체에 관한 자세한 내용은 MDN Web Docs에서 확인할 수 있습니다.
  • params: 라우트의 동적 경로 매개변수를 포함하는 객체입니다. 예를 들어 라우트 경로가 /users/$id이고 /users/123에 요청하면 params{ id: '123' }이 됩니다. 동적 경로 매개변수와 와일드카드 매개변수는 이 가이드의 뒷부분에서 다룹니다.
  • context: 요청의 컨텍스트를 포함하는 객체입니다. 미들웨어 간에 데이터를 전달할 때 유용합니다.

요청 처리를 완료하면 Response 객체나 Promise<Response>을 반환할 수 있으며, @tanstack/react-start의 헬퍼를 사용하여 응답을 조작할 수도 있습니다.

동적 경로 매개변수

서버 라우트는 TanStack Router와 동일한 방식으로 동적 경로 매개변수를 지원합니다. 예를 들어 이름이 routes/users/$id.ts인 파일은 동적 id 매개변수를 받는 /users/$id API 라우트를 생성합니다.

// routes/users/$id.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/users/$id')({
server: {
handlers: {
GET: async ({ params }) => {
const { id } = params
return new Response(`User ID: ${id}`)
},
},
},
})

// Visit /users/123 to see the response
// User ID: 123

하나의 라우트에 여러 동적 경로 매개변수를 사용할 수도 있습니다. 예를 들어 이름이 routes/users/$id/posts/$postId.ts인 파일은 두 개의 동적 매개변수를 받는 /users/$id/posts/$postId API 라우트를 생성합니다.

// routes/users/$id/posts/$postId.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/users/$id/posts/$postId')({
server: {
handlers: {
GET: async ({ params }) => {
const { id, postId } = params
return new Response(`User ID: ${id}, Post ID: ${postId}`)
},
},
},
})

// Visit /users/123/posts/456 to see the response
// User ID: 123, Post ID: 456

와일드카드/Splat 매개변수

서버 라우트는 경로 끝의 와일드카드 매개변수도 지원하며, 이는 뒤에 아무것도 없는 $로 표시합니다. 예를 들어, 이름이 routes/file/$.ts인 파일은 와일드카드 매개변수를 허용하는 /file/$ API 라우트를 생성합니다.

// routes/file/$.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/file/$')({
server: {
handlers: {
GET: async ({ params }) => {
const { _splat } = params
return new Response(`File: ${_splat}`)
},
},
},
})

// Visit /file/hello.txt to see the response
// File: hello.txt

본문이 있는 요청 처리하기

POST 요청을 처리하려면 라우트 객체에 POST 핸들러를 추가할 수 있습니다. 핸들러는 요청 객체를 첫 번째 인수로 받으며, request.json() 메서드를 사용하여 요청 본문에 접근할 수 있습니다.

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
POST: async ({ request }) => {
const body = await request.json()
return new Response(`Hello, ${body.name}!`)
},
},
},
})

// Send a POST request to /hello with a JSON body like { "name": "Tanner" }
// Hello, Tanner!

이는 PUT, PATCH, DELETE 같은 다른 HTTP 메서드에도 적용됩니다. 라우트 객체에 이러한 메서드의 핸들러를 추가하고 적절한 메서드를 사용하여 요청 본문에 접근할 수 있습니다.

request.json() 메서드는 요청의 파싱된 JSON 본문으로 이행되는 Promise를 반환한다는 점을 기억해야 합니다. 본문에 접근하려면 결과에 await를 사용해야 합니다.

이는 서버 라우트에서 POST 요청을 처리하는 일반적인 패턴입니다/ 요청 본문에 접근할 때 request.text() 또는 request.formData() 같은 다른 메서드도 사용할 수 있습니다.

JSON으로 응답하기

Response 객체를 사용하여 JSON을 반환할 때 일반적으로 다음 패턴을 사용합니다:

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
GET: async ({ request }) => {
return new Response(JSON.stringify({ message: 'Hello, World!' }), {
headers: {
'Content-Type': 'application/json',
},
})
},
},
},
})

// Visit /hello to see the response
// {"message":"Hello, World!"}

Response.json 헬퍼 함수 사용하기

또는 Response.json 헬퍼 함수를 사용하여 Content-Type 헤더를 application/json로 자동 설정하고 JSON 객체를 직렬화할 수 있습니다.

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
GET: async ({ request }) => {
return Response.json({ message: 'Hello, World!' })
},
},
},
})

// Visit /hello to see the response
// {"message":"Hello, World!"}

상태 코드로 응답하기

Response 생성자의 두 번째 인수에 속성으로 전달하여 응답의 상태 코드를 설정할 수 있습니다

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/hello')({
server: {
handlers: {
GET: async ({ request, params }) => {
const user = await findUser(params.id)
if (!user) {
return new Response('User not found', {
status: 404,
})
}
return Response.json(user)
},
},
},
})

이 예시에서는 사용자를 찾을 수 없으면 404 상태 코드를 반환합니다. 이 메서드를 사용하여 유효한 모든 HTTP 상태 코드를 설정할 수 있습니다.

응답에 헤더 설정하기

응답에 헤더를 설정해야 할 때가 있습니다. Response 생성자의 두 번째 인수로 객체를 전달하여 설정할 수 있습니다.

// routes/hello.ts
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/hello')({
server: {
handlers: {
GET: async ({ request }) => {
return new Response('Hello, World!', {
headers: {
'Content-Type': 'text/plain',
},
})
},
},
},
})
// Visit /hello to see the response
// Hello, World!