TanStack / TanStack/router

Incorrect schema composition in middleware and server functions

Open
#5,035 3 comments 5 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Which project does this relate to?

Start

Describe the bug

Consider defining schema validation in middleware and server functions, like such:

import { createFileRoute } from '@tanstack/react-router'
import { createMiddleware, createServerFn } from '@tanstack/react-start';
import { z } from 'zod'

export const Route = createFileRoute('/')({
  component: Home,
})

export const fooWare = createMiddleware({
  type: 'function',
})
  .validator(z.object({ foo: z.string() }))
  .server(async ({ next }) => {
    return next();
  });

export const barWare = createMiddleware({
  type: 'function',
})
  .validator(z.object({ bar: z.string() }))
  .server(async ({ next }) => {
    return next();
  });

export const bazFunc = createServerFn({
  method: 'POST',
  response: 'data',
})
  .middleware([fooWare, barWare])
  .validator(
    z.object({
      baz: z.string(),
    }),
  )
  .handler(
    async ({ data }) => {
      console.log({ data })

      return { data }
    })

function Home() {
  return (
    <div className="p-2">
      <button onClick={async () => {
        console.log({
          data: await bazFunc({
            data: {
              foo: 'foo',
              bar: 'bar',
              baz: 'baz',
            }
          })
        })
      }}>
        Click me!
      </button>
    </div>
  )
}

When you hover data in the server function, it looks like you can access all properties validated in the middleware as well as the function:

Image

And indeed, if you leave out any properties in the call, you get an error:

Image

However, when you click the button in this example, you get a server-side error:

Server Fn Error!

Error: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "undefined",
    "path": [
      "bar"
    ],
    "message": "Required"
  }
]

And a client-side error as well:

Image

This is because z.object drops unknown properties, and so starting at fooWare, everything other than foo gets dropped.

Workaround

You can work around this by using z.looseObject instead of z.object.

Your Example Website or App

https://github.com/knpwrs/tanstack-start-middleware-validation-composition-error/blob/a9afea806c570600b695c301dd6551a4cb42f841/src/routes/index.tsx

Steps to Reproduce the Bug or Issue
  1. Start the app
  2. Click the button
Expected behavior

There are a few ways to solve this:

  1. Middleware could only use schemas for validation and not for transforming. I don't like this solution, it implies lots of other changes.
  2. Somehow change the schema inference to use composition. Instead of effectively z.infer<typeof FooSchema> & z.infer<typeof BarSchema> & z.infer<typeof BazSchema> since this isn't accurate.
  3. Document that folks should be careful with z.object, but type inference is still broken.
Screenshots or Videos

No response

Platform
  • Router / Start Version: 1.131.28
  • OS: macOS
  • Browser: Firefox
  • Browser Version: 142.0
  • Bundler: vite
  • Bundler Version: 7.1.3
Additional context

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reproduction at src/routes/index.tsx and run the app, then trace how middleware and server-function validators compose schemas. Compare the inferred foo/bar/baz data with the runtime behavior of z.object dropping unknown properties. Done means the validated request and its TypeScript type agree while missing required fields remain rejected.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
api, backend, backend-api-design
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.