Best way to handle "not found" on `ItemId` lookups

When performing a lookup on an model using

filter: { id: { eq: $id } }

and no object exists with that exact id the error is not very explicit or useful.

I have a strong preference for explicit error return objects in GraphQL to handle this kind of thing, but the Dato GraphQL is probably not going to adapt those any time soon. So I ask generally, what’s the best way to handle “not found” scenarios like this? Currently I’m running a string comparison on the error message itself, but it feels wrong.

Hey @jokull,

Wouldn’t this just be a null check, e.g. if(post) or more explicitly if(post !== null)?

It seems to me that not-found posts would just be null:

Or if you need a more explicit “this entry is good” check, you can validate post._status==='published' and/or post._isValid===true, perhaps?

I’m not sure of your use case here (why would it ever be looking up a nonexistent ID to begin with?), but if you can provide an example, maybe we can help think of something more suitable?

That’s interesting, I’m definitely not getting null, that would actually be a better scenario. I think I’m hitting some kind of regex checking for the old and new id formats. If you try adding a dash or something to the id to make it invalid looking, what happens?

The reason I’m getting non-existing id’s is just because of some bot traffic I think. I’m just seeing this come up in my logs and don’t want a 500 error but a nice 404.

Forgot to add, this is the error message I’m getting and checking for:

Variable $id of type ItemId! was provided invalid value

Hey @jokull,

I see, thank you for explaining! That does indeed happen if you provide altogether invalid IDs (like if a bot were just scanning for them and making up IDs).

First, I should probably point out that it’s not a great idea to have your page routing directly tied to API lookups, if for no other reason than that this would make for easy denial-of-service attacks from any bot going to your pages (existent or not). You would then have to pay for all their pointless API calls :frowning: It would be safer to route only to pages (IDs) you know you’ve created and to 404 or redirect other ones by default, unless you are specifically trying to dynamically generate pages for SEO or such.

That said, if you really want to do this, you can add a check to your router to first test to see if the ID is a valid Dato ID before you make the API call. To do that, you can import the isValidId() method from our JS client lib (or just copy and paste that file into your script): js-rest-api-clients/packages/cma-client/src/idUtils.ts at main · datocms/js-rest-api-clients · GitHub

That will test the ID for validity so you don’t pass it on to the API if it’s junk.

(Edit: The package has since been updated. The below comment is no longer relevant.)

(Outdated, please ignore): Click to expand: Only relevant if your Dato project still has number-only IDs

Side note: If your Dato project is really old, and not using alphanumeric IDs yet (i.e., it still has integer IDs from our past), you can also allow through 6-byte integer IDs (i.e., ints <= 281474976710655). That check is NOT present in the isValidId() option but IS in the GraphQL layer, which is why integer IDs still work there. Newer projects don’t have to worry about this at all.

Does that help?

That helps - and thanks for including that note on integer ID’s - we do indeed have a lot of those. SEO is very important for us so we are just rendering everything dynamically. This check will really help prevent attacks that rack up our billing.

When looking at that code for isValidId, I notice that the first lines are:

  // For backward compatibility, first check to see if this is an older-style integer ID formerly used by Dato
  if (/^\d+$/.test(id)) {
    const intId = BigInt(id);
    const maxDatoIntegerId = 281474976710655; // Max 6-byte/48-bit unsigned int
    return intId <= maxDatoIntegerId;
  }

This directly contradicts your comment that legacy ID checking isn’t supported. Unless I’m misunderstanding something …

Oh, hah, that was fast! I made a PR for that because of your post here. I didn’t expect it to get merged in so soon (it usually takes a few days, but our devs were on it this time!).

That means you can just go ahead and use that code then. Sorry for the confusion. It wasn’t there yet when I first replied :slight_smile:

1 Like

Looks like it still needs a release. Looks like it was merged a day after the 3.3.3 release. Great support, thank you!

Good call. I’ve asked for a version release and will let you know once it’s up. (If it’s time sensitive, you could also copy that code or fork the repo to get it working sooner).

pnpm patch to the rescue :wink:

1 Like

What, is it out of fashion to just SSH into prod and copy-paste into the bundle now? :wink:

3.3.4 is out now and includes that fix: