best practices to throw error on backend

I see people using throw new error and throw error But for me i kinda prefer having standard patterns across all the apis
return {
status: false,
error message
}
return {
status: false,
error message
}
17 Replies
Mocha
Mocha11mo ago
Errors in JavaScript world are too unpredictable and messy for throw to be useful to me. so yes, I'd either return
{ value: TValue | null, error: TError | null }
{ value: TValue | null, error: TError | null }
or if I don't care about the error message itself, I usually just
TValue | undefined
TValue | undefined
I'd say the first is a best practice for a library (that's what libraries like TanStack's do), and the other one is up to you to decide for your case 1
async function randomDog(breed: string): Promise<{url?: string, err?: string}> {
return await fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
.then(res => res.json())
.then(data => ({ url: data.message }))
.catch(err => ({ err })) // could be user input error
}

const { err, url: huskyDog } = await randomDog('Husky')
if (err) return
console.log(huskyDog)
async function randomDog(breed: string): Promise<{url?: string, err?: string}> {
return await fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
.then(res => res.json())
.then(data => ({ url: data.message }))
.catch(err => ({ err })) // could be user input error
}

const { err, url: huskyDog } = await randomDog('Husky')
if (err) return
console.log(huskyDog)
2
async function randomDogImage(): Promise<string | undefined> {
return await fetch('https://dog.ceo/api/breeds/image/random')
.then(res => res.json())
.then(data => data.message)
.catch(() => undefined) // non-user error
}

const dog = await randomDogImage()
if (!dog) return
async function randomDogImage(): Promise<string | undefined> {
return await fetch('https://dog.ceo/api/breeds/image/random')
.then(res => res.json())
.then(data => data.message)
.catch(() => undefined) // non-user error
}

const dog = await randomDogImage()
if (!dog) return
Benjamin
Benjamin11mo ago
await + then() ? 🤔 Choose your weapon lol
Mocha
Mocha11mo ago
lol I only await the value I care about. I don't need res itself so I won't assign it to a variable
Benjamin
Benjamin11mo ago
It looks weird to mix them up imo. The whole point of async await is to get rid of them() 😅 Plus it's confusing about what is actually returned here, you force the reader to pay more attention about what's finaly returned
Mocha
Mocha11mo ago
I agree it may be confusing, but especially with fetch and .json, I find it more appealing than:
async function awaits(): Promise<string | undefined> {
const res = await fetch('https://dog.ceo/api/breeds/image/random')
if (!res.ok) return undefined
const data = await res.json()
if (!data) return undefined
return data.message
}
// or
async function saferAwaits(): Promise<string | undefined> {
try {

const res = await fetch('https://dog.ceo/api/breeds/image/random')
const data = await res.json()
return data.message

} catch (err) {
return undefined
}
}
async function awaits(): Promise<string | undefined> {
const res = await fetch('https://dog.ceo/api/breeds/image/random')
if (!res.ok) return undefined
const data = await res.json()
if (!data) return undefined
return data.message
}
// or
async function saferAwaits(): Promise<string | undefined> {
try {

const res = await fetch('https://dog.ceo/api/breeds/image/random')
const data = await res.json()
return data.message

} catch (err) {
return undefined
}
}
I wouldn't use then in more complex cases, but for this one, I'll almost always do the same: fetch, json, & extract result. Do you have a cleaner way that also handles all possible errors?
Benjamin
Benjamin11mo ago
The last one looks less confusing if I was the reviewer, I'd spend less time thinking about what's going on For me the last example you provided is good enough, it's clear
Mocha
Mocha11mo ago
Fair enough, and almost the same amount of code. I just have variable-phobia in JS I guess lol
Benjamin
Benjamin11mo ago
Haha I understand, but at least it provides documentation without writing documentation 😁
scot22
scot2211mo ago
I just use errors And throw them I worked on a project that used to use monads Then we stopped and ripped them out
Mocha
Mocha11mo ago
@Alex | C4 model stan How do you make sure all possible Errors are handled? Do you just always try-catch? If so, where do you try?
scot22
scot2211mo ago
You can’t make sure all errors are handled, the program could always just crash I subscribe to the “let it crash” philosophy for some errors, and then for known errors I try to catch where it makes sense (as close as possible to the source)
barry
barry11mo ago
I never throw errors in utility functions. I return Error or Type Basically like Result in Rust Then I check where I use it If it's of type error, then there, I handle it. Instead of an ugly try catch block
const barrysWayIsTheWay = (): Promise<string | Error> => {
const fetch_query = await fetch("").then((fetch_result) => fetch_result.json()).catch((error) => {
return new Error("Fetching ...something... failed")
})

if (fetch_query instanceof Error) {
return fetch_query
}

if (typeof fetch_query?.message !== "string") {
return new Error("Message not of type string but of unexpected type " + typeof fetch_query?.message)
}

return fetch_query.message
}
const barrysWayIsTheWay = (): Promise<string | Error> => {
const fetch_query = await fetch("").then((fetch_result) => fetch_result.json()).catch((error) => {
return new Error("Fetching ...something... failed")
})

if (fetch_query instanceof Error) {
return fetch_query
}

if (typeof fetch_query?.message !== "string") {
return new Error("Message not of type string but of unexpected type " + typeof fetch_query?.message)
}

return fetch_query.message
}
I would validate it with Valibot too but you get the idea
Mocha
Mocha11mo ago
Love this, and yes, Rust's Result and Option are awesome.
export async function evenBetterer(): Promise<string | Error> {
const fetch_query = await fetch('')
.then(res => res.json() as Promise<{ message: string }>)
.catch(() => new Error('Fetching ...something... failed'))

if (typeof fetch_query?.message !== 'string')
return new Error('Message not of type string')

return fetch_query instanceof Error
? fetch_query
: fetch_query.message
}
export async function evenBetterer(): Promise<string | Error> {
const fetch_query = await fetch('')
.then(res => res.json() as Promise<{ message: string }>)
.catch(() => new Error('Fetching ...something... failed'))

if (typeof fetch_query?.message !== 'string')
return new Error('Message not of type string')

return fetch_query instanceof Error
? fetch_query
: fetch_query.message
}
barry
barry11mo ago
still need to check its of type string just because you cast it as it doesnt mean it is I tweaked the one above. Still need a ?. Accessing undefined will make the server shit itself
Mocha
Mocha11mo ago
That's a "this is fine" meme right here lol. With all of that, validation is still very much needed in serious apps I suspect Error would result in some weird behavior if used in tRPC since it's an instance of a class, not just a normal object? but otherwise I'd say this is clean and easy to work with "used in" as in, returned from a tRPC function and used like an Error class instance in client side
barry
barry11mo ago
Also, I highly recommend you look into https://ts-rest.com/
🪄 | ts-rest
Incrementally adoptable type-safety for your new and existing APIs
scot22
scot2211mo ago
I agree with prime that using monads in ts is just too messy Great in rust Not so great in ts When I was a junior I used to use result types a lot but less so now