Creating routes

The only thing you need to create a route is to wrap a function that you would write in the backend with the createRoute function. It will return a function with the same signature as the function you passed to it, except that it will do a request under the hood.

import { createRoute } from 'agrume'

const sayHello = createRoute(
  async () => {
    return 'Hello world!'
  },
)

Note

sayHello will be typed as () => Promise<string>.

Note

The above code will be transformed to

async function sayHello() {
  return fetch('/api/sayHello', { method: 'POST' }).then((response) => response.json())
}

You can then use the sayHello function to do a request to the route:

sayHello().then(console.log) // Hello world!

You don’t have to necessarily return a value. Your function can have a signature like (parameters: T) => Promise<void>. In this case, the HTTP response will be 204 No Content.

import { createRoute } from 'agrume'

const sayHello = createRoute(
  async (name: string) => {
    console.log(`Hello ${name}!`)
  },
)

Warning

At the moment you can only use the createRoute function in .js, .jsx, .ts and .tsx files. To use Agrume in other files, you need to export the createRoute function from one of the valid files and import it into the other files. (See Vue example)

Parameters

You can request parameters from the client just like you would do with a normal function:

import { createRoute } from 'agrume'

const sayHello = createRoute(
  async (name: string) => {
    return `Hello ${name}!`
  },
)

You can then use the sayHello function to do a request to the route:

sayHello('Arthur').then(console.log) // Hello Arthur!

Note

Agrume is type-safe so if you don’t pass the correct parameters to the function, your IDE will warn you!

Note

Agrume will pass the parameters to the server as body parameters so every request will be a POST request.

Realtime routes

You can use the createRoute function to create a realtime route. It can replace WebSockets.

clientserver

To send data from the client to the server in real time, you can require a generator function as a parameter of your route.

import { createRoute } from 'agrume'

const realtime = createRoute(
  async (clicks: AsyncGenerator<[number, number]>) => {
    for await (const [x, y] of clicks) {
      console.log(x, y)
    }
  },
)

Then, you can use the realtime function as follows:

realtime(async function* () {
  while (true) {
    yield [Math.random(), Math.random()]
    await new Promise((resolve) => setTimeout(resolve, 1000))
  }
})

The code above will send random coordinates every second. The server will log the coordinates in real time.

serverclient

To send data from the server to the client in real time, you can pass a generator function to the createRoute function.

import { createRoute } from 'agrume'

const realtime = createRoute(
  async function* () {
    while (true) {
      yield new Date().toISOString()
      await new Promise((resolve) => setTimeout(resolve, 1000))
    }
  },
)

The code above will send the current date of the server every second.

You can then use your function like a normal generator function:

for await (const date of await realtime()) {
  console.log(date)
}

Combining both (clientserver)

You can receive and send data in real time by combining both methods:

import { createRoute } from 'agrume'

const chat = createRoute(
  async function* (userMessages: AsyncGenerator<string>) {
    (async () => {
      // Receive messages in parallel with sending messages
      for await (const message of userMessages) {
        sendToAll(message)
      }
    })()

    for await (const message of allMessagesIterator) {
      yield message
    }
  },
)

By passing a generator function (the messages from your user) to the chat function, you can receive messages from all other users in real time.

Error throwing

You can use the http-errors package to throw a custom HTTP error. Agrume re-exports http-errors in a HTTPError member. You don’t need to install the package yourself.

import { createRoute } from 'agrume'
import { HTTPError } from 'agrume/errors'

const sayHello = createRoute(
  async (name: string) => {
    throw HTTPError.ImATeapot()
    return `Hello ${name}!`
  },
)

Options

You can configure each route individually by passing an object to the createRoute function.

path

You can specify the path of the route by passing a string starting with / to the path option:

import { createRoute } from 'agrume'

const getDogImage = createRoute(
  async () => {}, {
    path: '/dog'
  },
)

getClient

By default, Agrume will transform the createRoute function into a function that can be called to do a request to the route. The default client will use the fetch API to do the request. However, you can specify your own client by passing a function to the getClient option.

For example, if you want use a custom server that listen on port 3000, you can do:

import { createRoute } from 'agrume'

const getDogImage = createRoute(
  async () => {},
  {
    getClient(requestOptions) {
      return async (parameters) => {
        const response = await fetch(
          `http://localhost:3000${requestOptions.url}`,
          {
            ...requestOptions,
            body: JSON.stringify(parameters)
          }
        )
        return response.json()
      }
    }
  },
)

Note

The parameters argument cannot be inferred by TypeScript, so it will be typed as any. You can type it yourself, but it must be the same type as the parameters of the route.

Important

getClient will affect the type of the route. For example, if your getClient function returns the requestOptions, the type of the route will be () => Promise<RequestOptions>.

The default getClient function can be found in the source code (search for the getDefaultClient function).

Have a look at the Recipes section to see what you can do with the getClient option.