liaisev5.3.1
/Reading responses

Guide

Reading responses

This page says how liaise reads a response body and what you get in data. Read it when an endpoint returns something other than JSON, such as a file or a CSV, or nothing at all.

A response can be JSON, text or a file. You say which with responseType on the endpoint, and liaise reads the body for you:

import { createApi, defineRequest } from 'liaise'

type User = { id: string; name: string }

// JSON is the default.
const getUser = defineRequest<User>()({ method: 'GET', path: '/users/:id' })

// A CSV export comes back as a string.
const exportCsv = defineRequest<string>()({ method: 'GET', path: '/reports/:id/csv', responseType: 'text' })

// A file comes back as a Blob.
const downloadFile = defineRequest<Blob>()({ method: 'GET', path: '/files/:id', responseType: 'blob' })

// A 204 No Content has no body, so data is undefined.
const deleteUser = defineRequest<undefined>()({
  method: 'DELETE',
  path: '/users/:id',
  responseType: 'none',
})

const api = createApi({
  baseUrl: 'https://api.example.com',
  requests: { getUser, exportCsv, downloadFile, deleteUser },
})

api.getUser gives you a parsed User, api.exportCsv a string, api.downloadFile a Blob, and api.deleteUser gives data: undefined when the server answers 204.

responseTypeHow the body is readdata
'json' (default)response.text(), then JSON.parse()the parsed value
'text'response.text()string
'blob'response.blob()Blob
'arrayBuffer'response.arrayBuffer()ArrayBuffer
'formData'response.formData()FormData
'none'not read (the stream is cancelled)undefined

Empty bodies and null

  • An empty body under 'json' is a 'parse' error. You declared JSON and the server sent none, so no value could honestly match your type. The error has the response’s own status (a 204 reports 204), the response, and '' in error.body.
  • A literal null body is not empty. It is valid JSON, so the call succeeds with data: null.

Endpoints that send no body

  • 'none' is for an endpoint that sends no body on success, such as a DELETE that answers 204, or 200 with an empty body. liaise reads nothing, data is undefined, and a body the server sends anyway is discarded. Its stream is cancelled, which frees the connection.
  • Declare 'none' with the response type undefined. defineRequest enforces this (Defining endpoints). Without defineRequest it is only a convention, and data is undefined at runtime whatever type you wrote.

Error bodies

A non-2xx body is still read into error.body, because an error body usually explains what went wrong. It is read the same way as a success body, except under 'none': there it is read as JSON, and error.body is null when it isn’t JSON.

const { error } = await api.deleteUser({ id: '42' })
if (error) {
  // A 409 { "error": "already deleted" } lands in error.body,
  // even though deleteUser declares responseType: 'none'.
  console.error(error.status, error.body)
}

error.body is typed unknown, because nothing promises what shape a server’s errors take. To use one, check it first. A type guard narrows it:

type Problem = { message: string; code?: string }

function isProblem(body: unknown): body is Problem {
  return typeof body === 'object' && body !== null && typeof (body as Problem).message === 'string'
}

const { error } = await api.getUser({ id: '42' })
if (error?.kind === 'http' && isProblem(error.body)) {
  show(error.body.message) // error.body is a Problem here
}

The validator you use for responses can do the same check. What error.body holds for every kind of failure is listed under ApiError.

Next: Validating responses checks at runtime that the body has the shape your type promises.

esc