Add token requirement to public TRPC endpoint to add to DB (create-t3-app)

I've created a public TRPC endpoint on the exampleRouter like so:

export const exampleRouter = createTRPCRouter({
// ... other routes
create: publicProcedure
.mutation(async ({ ctx, input }) => {
// Fetch/scrape data from somewhere
// ...

// Add to DB
const newItem = ctx.prisma.example.create({
data: {
someData: "fetched data goes here",
},
});

return newItem;
}),
});

export const exampleRouter = createTRPCRouter({
// ... other routes
create: publicProcedure
.mutation(async ({ ctx, input }) => {
// Fetch/scrape data from somewhere
// ...

// Add to DB
const newItem = ctx.prisma.example.create({
data: {
someData: "fetched data goes here",
},
});

return newItem;
}),
});
The above works if I POST to /api/trpc/example.create. I want to be able to call this from Github Actions (this part seems pretty trivial), but I don't want just anyone to be able to call the route in order to trigger it, so I thought I should add a token:

export const exampleRouter = createTRPCRouter({
// ... other routes
create: publicProcedure
.input(
z.object({
token: z.string(), // Token for authentication
})
)
.mutation(async ({ ctx, input }) => {
if (input.token !== "SECRETSAUCE") { // Use .env variable
throw new Error("Invalid token");
}
// Fetch/scrape data from somewhere
// ...

// Add to DB
const newItem = ctx.prisma.example.create({
data: {
someData: "fetched data goes here",
},
});

return newItem;
}),
});

export const exampleRouter = createTRPCRouter({
// ... other routes
create: publicProcedure
.input(
z.object({
token: z.string(), // Token for authentication
})
)
.mutation(async ({ ctx, input }) => {
if (input.token !== "SECRETSAUCE") { // Use .env variable
throw new Error("Invalid token");
}
// Fetch/scrape data from somewhere
// ...

// Add to DB
const newItem = ctx.prisma.example.create({
data: {
someData: "fetched data goes here",
},
});

return newItem;
}),
});
But I get the following error when trying to POST to the endpoint with the following body:
{
"token": "SECRETSAUCE"
}
{
"token": "SECRETSAUCE"
}
tRPC failed on example.create: [
{
"code": "invalid_type",
"expected": "object",
"received": "undefined",
"path": [],
"message": "Required"
}
]
tRPC failed on example.create: [
{
"code": "invalid_type",
"expected": "object",
"received": "undefined",
"path": [],
"message": "Required"
}
]
(Post continued in comment below)
4 Replies
epsilon42
epsilon4217mo ago
(Post continued from above) I then realised that for all I've seen so far with TRPC, the data is all within the query string, not in the body of the request. So I guess I could change the request and change it so that it's something like /api/trpc/example.create?token=SECRETSAUCE, (probably looks a bit different to this - haven't tried this yet) but I have read that this is bad practice because it exposes the token in logs etc. So my first question is: - Is it okay to add token in query string, or is there a way of extracting the data from the body of the request to validate the token sent in the body? Some other questions: - Should I be using protectedProcedure somehow or is publicProcedure the right one to use here? - Should I be avoiding TRPC altogether (the example code below seems to work when POSTing a token in the body but not sure how to add something to DB)?
// Creating a non-TRPC endpoint like below works, but not sure how to add to DB
// src/pages/api/cron-test.ts
import type { NextApiRequest, NextApiResponse } from "next";

function validateToken(token: string): boolean {
return token === "SECRETSAUCE";
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
const { token } = req.body as { token: string };

if (!token || !validateToken(token)) {
res.status(401).json({ message: "Invalid or missing token" });
return;
}

// ... Add to DB here
res.status(200).json({ message: "POST request handled with token" });
} else {
res.status(405).json({ message: "Method Not Allowed" });
}
}
// Creating a non-TRPC endpoint like below works, but not sure how to add to DB
// src/pages/api/cron-test.ts
import type { NextApiRequest, NextApiResponse } from "next";

function validateToken(token: string): boolean {
return token === "SECRETSAUCE";
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") {
const { token } = req.body as { token: string };

if (!token || !validateToken(token)) {
res.status(401).json({ message: "Invalid or missing token" });
return;
}

// ... Add to DB here
res.status(200).json({ message: "POST request handled with token" });
} else {
res.status(405).json({ message: "Method Not Allowed" });
}
}
There's a very real possibility that I'm approaching this the wrong way so please let me know if I should be doing something differently. I'm coming from a frontend background and quite new to backend/API stuff.
dan
dan17mo ago
If you want to call the endpoit remotely (aka not using tRPC), then its better to abstract out the trpc function into another function then use that function within the trpc endpoint and a nextjs [apps/pages]/api endpoint and use the api endpoint on github
epsilon42
epsilon4217mo ago
Is the reason you're suggesting abstracting it to another function just in the case that I want to share the same logic/functionality within tRPC? In this particular case I don't see the functionality needing to be called from within the app, only triggered externally. Does this mean I would only need to have the logic in pages/api/my-externally-called-function.ts ?
dan
dan17mo ago
Yes, using trpc for external use just complicates things and its not what it is designed for.
Want results from more Discord servers?
Add your server