Error handling with OpenAPI Generator's typescript-fetch client

matt

I use OpenAPI Generator's typescript-fetch client in most projects I build. For the most part, it does the job, but it wasn't super obvious how I could capture errors as part of my response.

Let's use this code as a jumping off point:

try {
  let something = await doSomething()
} catch (error) {
  console.log(error)
}

A lot of examples that you'll find on the internet do exactly this and then stop there. The result of such an error is generally, ~"An error response was received." This seems easy enough and probably works for many naive usecases, but I have an editor page that talks to an API that can give pretty descriptive errors depending on the response code. I wanted to be able to pop those in a toast and update the form so that I knew what went wrong because I forget my own business logic all the time. I was also kind of tired of looking in Developer Tools to know what went wrong.

I started by making my error schema official. Django-Ninja comes with a built-in ValidationError that has a single field detail.

from ninja import Schema

class ValidationErrorResponse(Schema):
    detail: str

Then I started catching the exceptions that aren't caught by Schema (example: uniqueness errors)

@posts_router.post(
    "/",
    response={200: PostDetails, 422: ValidationErrorResponse},
    tags=["posts"],
    auth=JWTAuth(permissions=StaffOnlyModify),
)
def create_post(request: HttpRequest, post: PostMutate):
    try:
        post = Post.objects.create(**post.dict(), author=request.user)
    except IntegrityError as err:
        logger.error("Error creating post", error=err)

        raise ValidationError("Post with this slug already exists") from err
    return post

You'll also notice I specified a 422 response code and applied the schema to it. Since Django Ninja validation errors always map to the schema, I didn't need to do any extra work there. There is one last bit of work I needed to do though, and that's on the actual typescript-fetch catch branch:

try {
  const post = await api.blogApiCreatePost({
    postMutate: {
      title: title,
      slug: slug,
      published: parseDate(published_at),
      content: content,
    },
  })

  id = post.id

  addToast({
    message: 'Created post!',
  })
} catch (error) {
  if (error instanceof ResponseError) {
    console.error('Error saving post: ', error.cause, error.message)

    const json = await error.response.json()
    const body = ValidationErrorResponseFromJSONTyped(json, false)

    addToast({
      message: 'Error saving post: ' + body.detail,
    })
  }
}

First, error is always unknown so you have to type assert it. Typescript-fetch comes with this type called ResponseError. There's also FetchError and RequiredError. In my case ResponseError is what I needed and from that I can garner my message using one of the utility functions that is generated with the response schema. In this case I get some fresh, typed goodness. Now, when I create a duplicate slug for a given year it tells me so in plain old English.

Back to top