liaisev5.3.1
/Sending data

Guide

Sending data

This page says where liaise puts each param you pass, and how it turns a body into what is sent. Read it when an API expects a particular query-string format, or when you send something other than JSON.

You pass one params object, and liaise puts each field where it belongs: in the path, the query string or the body. You never build a URL by hand.

import { createApi, defineRequest } from 'liaise'

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

const listItems = defineRequest<Item[], { page: number; tags: string[] }>()({
  method: 'GET',
  path: '/orgs/:org/items',
})
const createItem = defineRequest<Item, { name: string }>()({
  method: 'POST',
  path: '/orgs/:org/items',
})

const api = createApi({ baseUrl: 'https://api.example.com', requests: { listItems, createItem } })

await api.listItems({ org: 'acme', page: 2, tags: ['a', 'b'] })
// GET /orgs/acme/items?page=2&tags=a&tags=b

await api.createItem({ org: 'acme', name: 'Lamp' })
// POST /orgs/acme/items, with the JSON body {"name":"Lamp"}

org fills the path in both calls. For the GET, the other params go in the query string, and an array becomes a repeated key. For the POST, they go in a JSON body, and liaise sets Content-Type: application/json. An endpoint can swap the two with bodyAs, as Query string or body shows.

Query strings

ParamsQuery string
{ page: 1, limit: 20 }?page=1&limit=20
{ tags: ['a', 'b'] }?tags=a&tags=b
{ filter: null }(left out)
{ filter: undefined }(left out)
{ meta: { nested: true } }Refused
{ since: new Date() }Refused
  • Arrays become repeated keys (tags=a&tags=b), the format most server frameworks read.
  • null and undefined are left out.
  • A nested object is refused. There is no standard way to put one in a query string (brackets, dots and JSON are all in use), so liaise doesn’t guess. Flatten it first.
  • A Date is refused too. APIs expect ISO 8601 or epoch milliseconds, so convert it yourself with date.toISOString() or date.getTime().
  • A refused param sends nothing. You get an error Result with kind: 'network' and a TypeError in error.body that names the param.

A baseUrl can carry a query string of its own, such as a fixed api-version. Its params go in front of the call’s (baseUrl query merging).

Request bodies

liaise serializes the body from what you pass, and sets Content-Type for you unless you set one yourself.

import { createApi, defineRequest } from 'liaise'

type User = { id: string; name: string }
type Order = { id: string }
type Line = { sku: string; qty: number }

const updateUser = defineRequest<User, { name: string }>()({
  method: 'PATCH',
  path: '/users/:id',
})
const placeOrder = defineRequest<Order, { customer: { id: string }; lines: Line[] }>()({
  method: 'POST',
  path: '/orders',
})
const createItems = defineRequest<{ created: number }, { name: string }[]>()({
  method: 'POST',
  path: '/items/bulk?dryRun=false',
})

const api = createApi({
  baseUrl: 'https://api.example.com',
  requests: { updateUser, placeOrder, createItems },
})

await api.updateUser({ id: '7', name: 'Grace' })
// PATCH /users/7, with the JSON body {"name":"Grace"}

await api.placeOrder({ customer: { id: 'c1' }, lines: [{ sku: 'lamp', qty: 2 }] })
// POST /orders, with the JSON body {"customer":{"id":"c1"},"lines":[{"sku":"lamp","qty":2}]}

await api.createItems([{ name: 'Lamp' }, { name: 'Desk' }])
// POST /items/bulk?dryRun=false, with the JSON body [{"name":"Lamp"},{"name":"Desk"}]
  • A path param fills the path and is left out of the body. id goes into /users/7, so the body is only {"name":"Grace"}. If your API also wants id in the body, give the body field a different name from the path param.
  • Nested objects and arrays go in the body as JSON. Only the query string refuses them.
  • An array as the params is the whole body. It is sent as a JSON array. An array has no named fields, so it can’t fill path params or a query string, and a call that needs either is refused before anything is sent.
  • A fixed query string can go in the path, as ?dryRun=false does here. Every param you pass goes either in the body or in the query string, never in both. For a query value that changes from call to call on a body request, see Send a query param with a JSON body.
You passBody sentContent-Type
null/undefinednull(none)
stringas-istext/plain
FormDataas-is(the browser sets the multipart boundary)
URLSearchParamsas-isapplication/x-www-form-urlencoded
Blobas-isapplication/octet-stream
ArrayBufferas-isapplication/octet-stream
Typed array, DataView, Bufferas-is (sent as binary)application/octet-stream
ReadableStreamas-is (a streaming upload; duplex: 'half' is set for you)application/octet-stream
Plain object or arrayJSON.stringify()application/json

A ReadableStream body can be sent only once. A retry, from middleware or from result.retry(), returns an error telling you to read the stream into a Blob or ArrayBuffer first (details). Upload and download files shows a FormData upload.

What params can be

You passWhat happens
Plain objectSplit into path params, query string and body
Map with string keysSame as the object it spells
Class instance with fieldsSame as a plain object (split by those fields even if the class also defines toJSON(); toJSON() is used only when there are no own fields)
Class instance with only toJSON()Sent as its JSON (body only; refused on a request whose params go in the query string)
ArraySent as a JSON array body, such as [{"name":"a"}]. Refused on a request whose params go in the query string, or whose path has params, since an array has no named fields to fill them
Typed array, DataView, Buffer, ReadableStreamSent as the body, as in the table above (refused on a request whose params go in the query string)
Set, a bare Date, a Map with non-string keys, a class with no fieldsRefused: an error Result (kind: 'network') naming the type. Nothing is sent.

A Map, Set or class with private state nested inside a JSON body is sent as {}, because that is what JSON.stringify does. Convert it first.

Headers, including your own Content-Type, follow the three levels of settings.

Next: Reading responses covers the other direction: how liaise reads what the server sends back.

esc