supabase / supabase-js

An isomorphic Javascript client for Supabase. Query your Supabase database, subscribe to realtime events, upload and download files, browse typescript examples, invoke postgres functions via rpc, invoke supabase edge functions, query pgvector.
https://supabase.com
MIT License
2.86k stars 220 forks source link

Major DX change: response and error handling #32

Closed thorwebdev closed 3 years ago

thorwebdev commented 3 years ago

Decision

We've decided to make a major change to support a better developer experience for working with supabase-js. Instead of throwing an error, we will return the error object as part of the response.

Example

Given a successful query, e.g. const response = await supabase.from('products').select('*'), response will be:

{
  "error": null,
  "data": [...],
  "status": 200,
  "statusCode": 200,
  "statusText": "",
  "body": [...] // for backwards compatibility: `body === data`
}

Given a bad query, e.g. const response = await supabase.from('productx').select('*'), response will be:

{
  "error": {
    "code": 404,
    "message": "relation \"public.productx\" does not exist",
    "details": null,
    "hint": null
  },
  "data": null,
  "status": 404,
  "statusCode": 404,
  "statusText": "",
  "body": null // for backwards compatibility: `body === data`
}

Future DX

This will enable the following experience:

const { error, data: products } = await supabase.from('products').select('*')
if (error) {
  alert(error.message)
  return // abort
}
console.log(`Found ${products.length} products.`)

Additional context

Inspiration has been taken from https://github.com/vercel/swr#quick-start:

import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

as well as Stripe.js : https://stripe.com/docs/js/payment_methods/create_payment_method image

soedirgo commented 3 years ago

Does this error handling include programmer errors? Say I pass a list to match where I expect an object, and I use Object.keys on it. Having to handle each and every kind of such exceptions in JS, with the appropriate error messages, is very painful IMO.

For the record, https://github.com/supabase/postgrest-js/pull/93 uses a fetch polyfill for the HTTP client, so there's no need to use the data field in place of body. Currently the error handling looks more like this, though we can shape it to your proposed format:

const { ok, body: products } = await supabase.from('products').select('*')
if (!ok) { // status code is not 2XX
  alert(products.message) // PostgREST's error is stored in body (`T | T[] | PostgrestError`) in case of error
  return // abort
}
console.log(`Found ${products.length} products.`)
kiwicopple commented 3 years ago

Does this error handling include programmer errors?

I don't think this should be expected, just catch and rethrow unexpected errors. As you say, handling every possible error would be unwieldy. We should just shape expected errors.

so there's no need to use the data field in place of body.

This one is just a preference. I guess we should decide now what is the more appropriate name for the key. IMO body seems like a throwback to when we used ajax used to request HTML snippets that would be injected into HTML.

Currently the error handling looks more like this

if (!ok) { // status code is not 2XX
  return // abort
}

I'd far prefer this one to look like the suggested change (if (error)). The term ok is unexpected, and error is very clear

kiwicopple commented 3 years ago

Tasks:

Also solves:

stupidawesome commented 3 years ago

Are there plans to expose the Content-Range header in the response object?

soedirgo commented 3 years ago

@stupidawesome is there a use case for this? PostgREST supports this, but we haven't really considered having this in. If you just want the COUNT, there's an open issue for it.

roker15 commented 2 years ago
const { error, data: products } = await supabase.from('products').select('*')
if (error) {
  alert(error.message)
  return // abort
}
console.log(`Found ${products.length} products.`)

Do i still need to use try-catch after using above pattern?

soedirgo commented 2 years ago

You don't - if it throws it might be sign of a postgrest-js bug.

roker15 commented 2 years ago

You don't - if it throws it might be sign of a postgrest-js bug.

So does supabase return network failure in errorobject? I think it does not. So what should i do then?

soedirgo commented 2 years ago

I'm not sure about the other dependencies, but postgrest-js should return network failure in error. If not, can you create an issue in supabase/postgrest-js along with your client lib verison?

roker15 commented 2 years ago
import useSWR from 'swr'

function Profile() {
  const { data, error } = useSWR('/api/user', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>
  return <div>hello {data.name}!</div>
}

Here is the text from swr documentation - **

"If an error is thrown inside fetcher, it will be returned as error by the hook." here is link

** So if supabase does not throw error and simply return responseobject how we will catch supabase error inside swr?

Will following code work then? Because in following code supabase is not throwing error, so what will be error in following code?

const { data, error } = useSWR(
    subheadingId == undefined || userid == undefined
      ? null
      : [`/subheading/${subheadingId}/${userid}`],
    async () =>
      await supabase.rpc<SharedNotesList>("subheadingg", {
        subheadingid: subheadingId,
        sharedwith: userid,
      }),
     );
soedirgo commented 2 years ago

If you want the .rpc() call to throw errors, you can use .throwOnError() (example: https://github.com/supabase/postgrest-js/blob/a972b993487fe99990a6be7995f0c6a2dc50b784/test/basic.ts#L136)

roker15 commented 2 years ago

If you want the .rpc() call to throw errors, you can use .throwOnError() (example: https://github.com/supabase/postgrest-js/blob/a972b993487fe99990a6be7995f0c6a2dc50b784/test/basic.ts#L136)

I am not specific about rpc. take it as general supabase query and answer what i am asking please.

soedirgo commented 2 years ago

This is not specific to .rpc(). You can use .throwOnError() on .select(), .insert(), etc. and it will throw errors instead of returning it in the response object. See the example I linked above.

n-sviridenko commented 1 year ago

Can we set this as a default behaviour globally via a parameter or smth? This is super unusual behaviour, as SDKs ussually throw errors, otherwise we have to add lots of additional checks around that which doesn't make sense.

rienheuver commented 11 months ago

Throwing errors by default, or have that as an option on createClient, would be a great improvement for us too. We use a lot of rxjs with supabase and regular error throwing would make the whole process more logical. Now we have to remember to write throwOnError after every query, because otherwise the error will go silent