Skip to content

setResponseStatus is not working for server routes #5377

@mturczyn

Description

@mturczyn

Which project does this relate to?

Start

Describe the bug

I was following the docs Server Routes

There is written that we can use setResponseStatus helper funciton in Responding with a status code

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

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

However it did not work.

Going further i found about the helper method for headers setResponseHeader which does work.

On the other side, both functions work when used in server function created with createServerFn (and used with useServerFn)

Below is my full "custom" example:

import {
    QueryClient,
    QueryClientProvider,
    useMutation,
    useQuery,
} from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
import { useState } from 'react'
import {
    setResponseHeader,
    setResponseStatus,
} from '@tanstack/react-start/server'
import { json } from '@tanstack/react-start'

const queryClient = new QueryClient()

// The most simple server route - returning a simple text response.
export const Route = createFileRoute('/server-routes/')({
    component: () => (
        <QueryClientProvider client={queryClient}>
            <RouteComponent />
        </QueryClientProvider>
    ),
    server: {
        handlers: {
            // Below, for PUT and POST we show two ways of returning JSON
            // and setting status codes.
            POST: async () => {
                setResponseStatus(201)
                return new Response(JSON.stringify({ hello: 'world' }), {
                    // status: 201,
                    headers: { 'Content-Type': 'application/json' },
                })
            },
            PUT: async () => {
                // Below does not work as of time of writing
                setResponseStatus(201)
                // For setting headers
                setResponseHeader('my-header', 'test')
                return json({ hello: 'world' })
            },
        },
    },
})

function RouteComponent() {
    const [someId, setSomeId] = useState('1')
    const [pathData, setPathData] = useState({ someData: '', someMoreData: '' })
    const [postJson, setPostJson] = useState<any>()
    const [putJson, setPutJson] = useState<any>()

    const post = useMutation({
        mutationKey: ['serverReoutesPost'],
        mutationFn: async () =>
            fetch('/server-routes', { method: 'POST' }).then((res) =>
                res.json()
            ),
        onSuccess: (data) => {
            setPostJson(data)
        },
    })

    const put = useMutation({
        mutationKey: ['serverReoutesPut'],
        mutationFn: async () =>
            fetch('/server-routes', { method: 'PUT' }).then((res) =>
                res.json()
            ),
        onSuccess: (data) => {
            setPutJson(data)
        },
    })

    const { data } = useQuery({
        queryKey: ['someData'],
        queryFn: async () =>
            fetch('/simple-server-route').then((res) => res.text()),
    })

    const { data: postData } = useQuery({
        queryKey: ['somePostData'],
        queryFn: async () =>
            fetch('/simple-server-route', { method: 'POST' }).then((res) =>
                res.text()
            ),
    })

    return (
        <div className="[&_pre]:bg-gray-200 [&_h1]:text-3xl [&_h1]:font-bold [&_h1]:m-5 p-10 [&_*]:p-2 [&_button]:border [&_button]:border-black [&_button]:cursor-pointer [&_button]:p-2 [&_button]:rounded-2xl [&_button]:active:scale-95">
            <h1>Server Routes</h1>
            <p>
                Most simple server route implement just GET handler and nothing
                else. Here's data from it(first GET, then POST):
            </p>
            <pre>{data}</pre>
            <pre>{postData}</pre>
            <p>Below example implementation:</p>
            <pre>
                {`export const Route = createFileRoute('/simple-server-route')({
    server: {
        handlers: {
            GET: async ({ request }) => {
                return new Response('Hello ' + request.url)
            },
        },
    },
})`}
            </pre>
            <h1>Dynamic Routes</h1>
            <form
                className="border"
                method="GET"
                action={`/server-routes/${someId}`}
            >
                <label>
                    Go To /server-routes/
                    <input
                        className="border-b"
                        value={someId}
                        onChange={(e) => setSomeId(e.target.value)}
                    />
                </label>
                <button type="submit">GoGoGo!</button>
            </form>
            <form
                className="border"
                method="GET"
                action={`/server-routes/${pathData.someData}/${pathData.someMoreData}`}
            >
                <label>
                    Go To /server-routes/
                    <input
                        className="border-b"
                        value={pathData.someData}
                        onChange={(e) =>
                            setPathData({
                                ...pathData,
                                someData: e.target.value,
                            })
                        }
                    />
                    /
                    <input
                        className="border-b"
                        value={pathData.someMoreData}
                        onChange={(e) =>
                            setPathData({
                                ...pathData,
                                someMoreData: e.target.value,
                            })
                        }
                    />
                </label>
                <button type="submit">GoGoGo!</button>
            </form>

            <h1>Splat pattern</h1>
            <p>
                $ is wildcard to catch all tokens in a route. It is "catch all
                handler". And it can be used with{' '}
            </p>
            <pre>
                const {'{'} _splat {'}'} = params
            </pre>
            <form method="GET" action={`/server-routes/splat/a`}>
                <button type="submit">Go to /server-routes/splat/a</button>
            </form>

            <h1>Returning JSON and status codes from a handler</h1>
            <p>
                We can return status codes and JSON from server routes. Below
                are examples and requests can be inspected in dev tools.
            </p>
            <button onClick={() => post.mutate()}>POST</button>
            {JSON.stringify(postJson)}
            <button onClick={() => put.mutate()}>PUT</button>
            {JSON.stringify(putJson)}
        </div>
    )
}

Your Example Website or App

attached directly in this post

Steps to Reproduce the Bug or Issue

Try to use setResponseStatus helper method

Expected behavior

As a developer, i expect this method to set status code on the response.

Screenshots or Videos

No response

Platform

  • Router / Start Version: [e.g. 1.121.0]
  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Browser Version: [e.g. 91.1]
  • Bundler: [e.g. vite]
  • Bundler Version: [e.g. 7.0.0]

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions