Best way to check for Not Found

When writing error handling for an item that does not exist in the database, it’s possible to check if an item was not found using either:

  if (err && err.name === 'NotFound') {
    //
  }

or

  if (err && err.requestResult.statusCode === 404) {
   //
 }

Which of these Fauna properties is considered the “proper” (i.e. most stable) way to test for this?

1 Like

Just to be clear, where does the error come from?
GraphQL? FQL + Javascript driver?

From FQL + Javascript library. I’m using the Fauna NPM library:

  let index = 'foo';
  let val = 'abc123'
  try {
    result = await client.query(q.Get(q.Match(q.Index(index), val)));
  } catch (err) {
    console.error(err);
  }

The most stable thing to do is to take control of the error handling. If the result of a part of your query can return null, then it would be advisable to handle that inside the query itself in a way that fits your application.

I have not seen a truly canonical way to do this bubble up to the surface yet. There are a couple of things I could suggest though.

abort with a message you app can interpret

Like throwing an error in your app.

let index = 'foo';
let val = 'abc123'
try {
  result = await client.query(
    q.Let(
      {
        ref: q.Match(q.Index(index), val),
      },
      q.If(
        q.Exists(q.Var('ref')),
        q.Get(q.Var('ref')),
        q.Abort('error message')
      )
  );
} catch (err) {
  console.error(err);
}

always return a result

This would be my preferred way to do things “nicely”.

If you’re into functional programming, think Result Monad. But I mean as simple as

const goodResult = {
  // optional type value,
  // not strictly necessary if you can otherwise distinguish good from bad results
  type: 'fooResult', 
  data: ...
}

const badResult = {
  type: 'Error'
  message: ...
  code: ...
}

You can apply this to the query:

let index = 'foo';
let val = 'abc123'
try {
  result = await client.query(
    q.Let(
      {
        ref: q.Match(q.Index(index), val),
      },
      q.If(
        q.Exists(q.Var('ref')),
        {
          type: 'fooResult',
          data: q.Get(q.Var('ref'))
        },
        {
          type: 'Error',
          message: 'item not found in index foo',
          code: 42
        }
      )
  );

 // handle bad results here, not in catch block.

} catch (err) {
 // Now only have to handle errors
  console.error(err);
}
2 Likes

hmm Those are alternatives, but it seems like this is already provided by the JS library two different ways:

err.name === 'NotFound'
err.requestResult.statusCode === 404

One of these must be intended for use by developers, no?

If one isn’t recommended over the other, I’ll just pick whichever and hope the faunadb NPM lib follows semver, to not break compatibility unexpectedly.

If you write a query that returns an error you get something like this:

If I want to be certain that it doesn’t change I typically look at the requestResult > responseContent > errors > and verify what the error code is as this should not change. This seems to be the same value as message though. I’ll ask internally whether the top level message or name is guaranteed to never change or not.

@Isle

I received an answer internally.
Although the top level message is the same as the code in the errors array. The guidance from our internal team is that you should use the error array since the message is created in the driver and does not come from the core. So in short:

  • do not use the message
  • do not use the error description
  • do use the error code

So in my above code, this is the part you should use:

It’s fairly nested so to do that easily I typically use a helper function.

const safeVerifyError = (error, keys) => {
  if (keys.length > 0) {
    if (error && error[keys[0]]) {
      const newError = error[keys[0]]
      keys.shift()
      return safeVerifyError(newError, keys)
    } else {
      return false
    }
  }
  return error
}

Which I typically then use as follows

const errorReason = safeVerifyError(err, [
    'requestResult',
    'responseContent',
    'errors', // The errors of the call
    0,
     'code'
])

or when it’s the underlying error of a UDF:

const errorReason = safeVerifyError(err, [
    'requestResult',
    'responseContent',
    'errors', // The errors of the call
    0,
    'cause',
    0,
    'code'
])

I opened a ticket to have more official guidance on how to handle the result (and the errors) of a query.

1 Like

This is true. I am very glad to see Brecht’s response as well.

My thought process, just to share, is that FQL is a LOT like a functional style of javascript, so I think of error handling in FQL space like I do any part of my javascript application, and I think of all the advice everywhere to avoid throwing errors.

Much of what I have focused on is GraphQL for basic CRUD stuff and UDFs for (potentially very) complex things. I have so far avoided having to deal with many small FQL CRUD queries, which I can see would work well catching the errors from Fauna.

Doing something beyond catching a 404 can be helpful once you have functions which call functions, which paginate over some data and create some other data with links to that previous data. This is an actual use case of mine: to do something like that and then kick off a few admin tasks on the DB, all in a single query call.

2 Likes

It depends how is your business logic.

A. If your business logic requires that value ‘abc123’ should be existed, then raising error is the right way.

B. If your business allows value ‘abc123’ may not exist, and you just want to check if ‘abc123’ exists for branching your codes, I think the [ptpaterson]'s answer is what you’re looking for: the best practice to check something exists in your collections.

This looks like a good practice. I’m just wondering about the number of calls it takes to do this. Is it a single call or does q.Exists and q.Get two separate calls? Pardon me is the question is silly, I’m just starting with this ecosystem.

1 Like

I have the same query as @poobesh . How does an FQL query be evaluated to count number of read/write calls?
Foe example:
q.Let(
{
ref: q.Match(q.Index(INDEX_VERIFICATION_REQUESTS_TOKEN), hashedToken),
},
q.If(q.Exists(q.Var(‘ref’)), q.Select(‘data’, q.Get(q.Var(‘ref’))), null),
)

1 Like

Hi @poobesh. Don’t worry about silly questions. FQL is my has got a learning curve for sure. There are better and not as good parts of the docs, but use all the docs to the best of your ability and play around a lot in the dashboard shell, and the knowledge will start to follow. Of course also keep asking questions when you can’t work something out.

FQL is made of composable functions. Get and Exist are such functions. You can compose them in a single query call. You can see both used in my examples above. You may note only a single await call.

@skris

Query cost is addressed in a different topic.

View costs with faunadb-js

Thank you @ptpaterson
Observer is helpful