TypeSafe external API ?

T_tweak^prone5/31/2023
I am currently communicating with an external API and after falling in love with TRPC, I would like to make this communication with the external API as TypeSafe as possible as well. I am however kinda failing in the basics of figuring out how to return different types, depending on the inputs of my function call. Example, i can call the same endpoint on my external API with the parameter "withItems", which kinda ends up mimicking the Prisma version of "include" - but unlike the great work Prisma has done - I am unsure how to detect if the programmer (in this case - mostly me), has passed this parameter to my API call function and return a different typesafe-type. I therefore always end up returning a mixed result (one type - or the other). My models are these (simplified):
interface Order{
id: number,
name: string,
date: Date,
}

interface Item{
name: string,
value: number,
}

interface Order_Items{
items: Item[];
}
interface Order{
id: number,
name: string,
date: Date,
}

interface Item{
name: string,
value: number,
}

interface Order_Items{
items: Item[];
}
My first attempts where to make a function call where I did something similar to this:
function getOrder(data:{
id:number,
withItems?: boolean,
}): Order | (Order&Order_Items) {
/* here i would call the external API with data as the POST data, but return two different types, if withItems was in the data or not */
}

const theOrder1 = getOrder({id: 1,})
const theOrder2 = getOrder({id: 1, withItems:true});
function getOrder(data:{
id:number,
withItems?: boolean,
}): Order | (Order&Order_Items) {
/* here i would call the external API with data as the POST data, but return two different types, if withItems was in the data or not */
}

const theOrder1 = getOrder({id: 1,})
const theOrder2 = getOrder({id: 1, withItems:true});
However my initial approach resulted in my having to typecast the result from getOrder - since I never succeded in making a "strong" connection between the parameters of the call, and the result. In comes Zod - which i believe would be my saviour here - but I fail to get Zod working. I simply cannot grasp my head around how I would implement Zod in this kind of structure and get type-safe return. Hope you can help (and I have formulated my question clear enough)
AAland5/31/2023
I'm not that good with TS either but setting up zod is easy. First you make a new file and export the schema and the type
import { z } from "zod";

export const testMutate = z.object({
test: z.string().trim()
});

export type TestMutate = z.infer<typeof testMutate>
import { z } from "zod";

export const testMutate = z.object({
test: z.string().trim()
});

export type TestMutate = z.infer<typeof testMutate>
Then in your API route you can parse the request body like this
import { testMutate } from '@/requests/test-mutate';
import { NextResponse } from 'next/server';

export async function GET(req: Request) {
let body = await req.json()
let data = testMutate.parse(body)

console.log("dataaa", data)

return NextResponse.json(data);
}
import { testMutate } from '@/requests/test-mutate';
import { NextResponse } from 'next/server';

export async function GET(req: Request) {
let body = await req.json()
let data = testMutate.parse(body)

console.log("dataaa", data)

return NextResponse.json(data);
}
You can also use the type TestMutate to make the mutation from the client.
Bbarry5/31/2023
GitHub
GitHub - mattpocock/zod-fetch: Simple function for building a type-...
Simple function for building a type-safe fetcher with Zod - GitHub - mattpocock/zod-fetch: Simple function for building a type-safe fetcher with Zod
T_tweak^prone5/31/2023
validating with Zod i can do - but how would this work with "type-return depends on the parameter in the function call" ? Thanks for the resource-paste - I can see the appeal, but I am not entirely sure how this relates to my problem. I would like to build a function, where the parameter in the function call, has a direct influence on the type(safe) return of the function. For example, the default fetcher doesnt seem to take into account varying returns from the API - depending on the data pasted into the API itself. I could surely do this with items?:Item[] in the object defintion of Zod, but what wouldn't exactly make it type safe in the level I want - I am looking for that "prisma feel", where my type is 100% what the API returns, without having to test if an object is in the return or not, when i use the data.
Bbarry5/31/2023
You can't, it either conforms to your type or fails. You can never be 100%
T_tweak^prone5/31/2023
Yes but the example you paste above is response-validation - I need response validation and typesafe return depending on the parameters sent to GET.
AAland5/31/2023
Why not have 2 functions for each call? i think that may be way easier than trying to figure out how to do it with TS, it gets complicated when you want to do stuff like that.
T_tweak^prone5/31/2023
I agree. It would be an easier way out - and its my "gotto failover". However, I feel this is a great place to learn more typescript / Zod, without taking the "easy way out" so to speak šŸ™‚
AAland5/31/2023
Well good luck, the last time i tried to go deep on TS i just wasted ~2 weeks, everything was going over my head. And even chatgpt didn't help, either it coldn't figure it out or i may be a bad Proompter. Rust was easier to learn than low level TS.
Bbarry5/31/2023
const Item = z.object({
id: z.number(),
...
})
const Items = z.array(Item)
const Order = z.object({})
const OrderWithItems = z.object({
...,
items: Items
})

type OrderWithItems = z.infer<OrderWithItems>
type OrderWithoutItems = z.infer<Order>

function getOrder(params: { id: number, withItems: true }): OrderWithItems;
function getOrder(params: { id: number, withItems: false }): OrderWithoutItems;
function getOrder(params: { id: number }): OrderWithoutItems;
function getOrder(params) {
if (!params) throw new Error("...");

if (!params.withItems) {
const result = ...
return Order.parse(result)
}

const result = ...

return OrderWithItems.parse()
}
const Item = z.object({
id: z.number(),
...
})
const Items = z.array(Item)
const Order = z.object({})
const OrderWithItems = z.object({
...,
items: Items
})

type OrderWithItems = z.infer<OrderWithItems>
type OrderWithoutItems = z.infer<Order>

function getOrder(params: { id: number, withItems: true }): OrderWithItems;
function getOrder(params: { id: number, withItems: false }): OrderWithoutItems;
function getOrder(params: { id: number }): OrderWithoutItems;
function getOrder(params) {
if (!params) throw new Error("...");

if (!params.withItems) {
const result = ...
return Order.parse(result)
}

const result = ...

return OrderWithItems.parse()
}
Why would this not do it though I don't get it
AAland5/31/2023
This looks great, you just need to make Items optional
Bbarry5/31/2023
no?
AAland5/31/2023
Well if there isn't items it will throw an error?
Bbarry5/31/2023
Why would it
AAland5/31/2023
Because it expects items to be defined
Bbarry5/31/2023
No It should only reach that if withItems has been set to true, at which we assume he has called the api assuming items will be there
AAland5/31/2023
You need to do
const Items = z.array(Item).optional()
const Items = z.array(Item).optional()
Bbarry5/31/2023
No
AAland5/31/2023
Well idk
T_tweak^prone6/3/2023
I am sorry, I am unable to say why this would not do it, as I am not as well versed in TS as you seem to be šŸ™‚ Its a in interested approach with more defined function-names refering to the same function - I was not aware / had forgotten this was possible in TS. I do believe this could or would give the desired result - at least it looks like a good match, if i interpret the code correctly. However I could be a bit worried that the complexity of this kind of solution would grow exponentionally with more optional variables being introduced, like withSeller, withBuyer, withStatistics etc. and as such the suggested solution might just cover the actual example i put forth above. The approach you have highlighted here, i believe I would be able to use right out of the box as is however - for this one specific case - but I would need a more generic approach, for multiple options - would you agree ? If anyone else wants to know the answer to this, I managed to build the below code:
T_tweak^prone6/3/2023
Rrocawear6/3/2023
But you are not validating it

Looking for more? Join the community!

Want results from more Discord servers?
Add your server
Recommended Posts
Want to request only half of the audio file but not workingi request an audio file from my server and stream it to the browser .. on the browser i make a requeHow is realtime DB syncing accomplished without Firestore/Supabase?Hi! I've been working with Firestore for a long time now and gotten very used to the realtime db synDrag and drop library recommendation?Whats the community-favorite option right now? I saw that react-beautiful-dnd isn't being maintainedtRPC invalid_type error with ClerkHi, I have this weird issue I've been trying to resolve for a while now. This is the error in browsSave selected option on dropdownHii, I have a dropdown list that is being made from an array and a Save button. When I click the savEnd-to-end testing tools, SuggestionsHey everyone! šŸ˜„ I hope you're all doing well. šŸ‘‹ I have a couple of questions for you, and I'd reN Nested RelationsHi im having trouble dealing with nested includes. I have a Comment and reply structure and replies A question on procedure design.I would like to know if it is best to try combine procedures or keep them separated. I don't want toMigrating to supabase from create-t3-turboHey guys! I'm migrating a create-t3-turbo app to the supabase create-t3-turbo template, and I'm a biSystem dark mode not detected when using darkMode: "media" in tailwind.config.tsHello! I am building a simple starter project using Tailwind and `shadcn-ui` components. I want the What is wrapping a next server action in startTransition doing?This was brought up in another question. I came up with the following explination, however this is jCan I call tRPC procedure inside another procedure?Something like this: ```js export const mainRouter = createTRPCRouter({ create: privateProcedure Anyone know how to go about using Tailwind Animation to animate a linethrough on hover?Or framer motion.Task scheduler infra?I'm looking for something that can do the following (or do essentially the same thing). It feels likNeed suggestions for better infra CICDalright, so for this project i use docker containers for everything. my compose file has a redis cacConditional render, why can't it be done?I understand the reason this happens it that *something* doesn't understand that the Loader already do something once server action is completedIs there a way to achieve this (see title)? Iā€™m using useTransition to start the action and need to How to use discord.js in Nextjs app dirHello, I am trying to use discord.js in my app but getting alot of errors, is this even possible?: `best way to fetch data in nextjsI was wondering what's the best way at the moment to query for external apis in NextJS? I know that @next/font turbo t3You might be using incompatible version of `@next/font` (13.4.4) and `next` (13.1.6). what version s