T
TanStack4mo ago
adverse-sapphire

`createServerFn` Zod validation works, but TS doesn’t catch invalid params at callsite?

I'm using createServerFn from @tanstack/react-start with Zod validation to define sendEmailFn. Everything works at runtime (thanks to .validator(...)), but TypeScript doesn't enforce the parameter shape when calling sendEmailFn()—I can omit fields or add invalid ones, and TS doesn’t complain. Only Zod throws at runtime. Why isn't TypeScript checking the input types at compile-time? Example code
import { createServerFn } from '@tanstack/react-start';
import { Resend } from 'resend';
import { z } from 'zod';

const resend = new Resend(process.env.RESEND_API_KEY);

const SendEmailFnParams = z.object({
to: z.email(),
subject: z.string(),
});

export const sendEmailFn = createServerFn({ method: 'POST' })
.validator((sendEmailParams) => SendEmailFnParams.parse(sendEmailParams))
.handler(async ({ data }) => {
const response = await resend.emails.send({
from: 'Test <test@test.com>',
to: data.to,
replyTo: process.env.EMAIL_ADDRESS_SUPPORT,
subject: data.subject,
text: 'This is a test email',
});

return response;
});
import { createServerFn } from '@tanstack/react-start';
import { Resend } from 'resend';
import { z } from 'zod';

const resend = new Resend(process.env.RESEND_API_KEY);

const SendEmailFnParams = z.object({
to: z.email(),
subject: z.string(),
});

export const sendEmailFn = createServerFn({ method: 'POST' })
.validator((sendEmailParams) => SendEmailFnParams.parse(sendEmailParams))
.handler(async ({ data }) => {
const response = await resend.emails.send({
from: 'Test <test@test.com>',
to: data.to,
replyTo: process.env.EMAIL_ADDRESS_SUPPORT,
subject: data.subject,
text: 'This is a test email',
});

return response;
});
I can call it with whatever params I want, what am I missing 🤔
await sendEmailFn({
data: {
to: guestEmail,
subject: 'Test email subject', // I can even remove subject field
no-tsc-error: 123
},
});
await sendEmailFn({
data: {
to: guestEmail,
subject: 'Test email subject', // I can even remove subject field
no-tsc-error: 123
},
});
5 Replies
fair-rose
fair-rose4mo ago
You should just pass the schema i believe. .validator(OrganizationSwitchSchema)
rising-crimson
rising-crimson4mo ago
use validator(SendEmailFnParams)
adverse-sapphire
adverse-sapphireOP4mo ago
sorry not following where should I use this?
rising-crimson
rising-crimson4mo ago
export const sendEmailFn = createServerFn({ method: 'POST' })
.validator(SendEmailFnParams)
.handler(async ({ data }) => {
const response = await resend.emails.send({
from: 'Test <test@test.com>',
to: data.to,
replyTo: process.env.EMAIL_ADDRESS_SUPPORT,
subject: data.subject,
text: 'This is a test email',
});

return response;
});
export const sendEmailFn = createServerFn({ method: 'POST' })
.validator(SendEmailFnParams)
.handler(async ({ data }) => {
const response = await resend.emails.send({
from: 'Test <test@test.com>',
to: data.to,
replyTo: process.env.EMAIL_ADDRESS_SUPPORT,
subject: data.subject,
text: 'This is a test email',
});

return response;
});
the way you used the schema was loosing the type information
adverse-sapphire
adverse-sapphireOP4mo ago
oh I see, thanks a lot!

Did you find this page helpful?