Function

A function is a JavaScript function that is executed in the Query Server and it has access to the databases.

The function should be in the format of:

export async function handleRequest(req) {
  return new Response("This is the body!", {
    status: 200,
    headers: {
      "content-type": "text/plain",
    },
  });
}

The function has to export a function called handleRequest that receives a Request and returns a Response.

To use a database you have to create a connection to the database:

const db = new Database("example.sql");

You can use it as a global variable or import it.

import { Database } from "query:database";

The Database constructor receives the name of the database. If the database is found, it will create a connection to the database; if not, it will create it first. It exposes the method query to read data from the database and write data in the database. The method query can have params, and those params are bound to the parameters based on the order of the array or an object with the format of :AAA, $AAA, or @AAA that serve as placeholders for values that are bound to the parameters at a later time.

As Query uses LiteFS proxy, you have to remember to use GET to read data and DELETE|POST|PUT|PATCH to write data.

Handle Request Example

// get.index.js
export async function handleRequest(req) {
  const db = new Database("example.sql");

  const result = await db.query("SELECT * FROM example WHERE id = ?", [1]);

  return new Response(JSON.stringify({ data: result }), {
    status: 200,
    headers: {
      "content-type": "application/json",
    },
  });
}

Query CLI provides an API to resolving routes against file-system paths and using the file names. To use functions it is required to follow the next structure:

Folder Structure Example

functions
├── get.index.js // GET "/"
├── post.index.js // POST "/"
├── example
    ├── get.index.js // GET "/example"
    └── get.[slug].js // GET "/example/:slug"
├── [slug]
    └── get.index.js  // GET "/:slug"
...

By default the folder to contain the functions has to be called functions. You can use another one by pointing to it, but we will explain it with more detail below.

It is important to note that the method used in a file is determined by the prefix (delete|get|patch|post|put).*, while the remaining part of the file name defines the final segment of the route. For instance, if the file name ends with index, it will be the root of the route, and if it is [slug], it will be a route with a slug. The slug is a placeholder for a value used in the route.

To define the different segments of the route, you must use the folder structure. For example, if you want to use the path /example/:slug, you have to create a folder called example and inside it a file called get.[slug].js. If you want to use the route /:slug, you have to create a folder called [slug] and inside of it a file called get.index.js. If you want to use the route /, you must create a file called get.index.js.

Query Cache Control

The Query Server has a feature that helps avoid compiling functions that have not been modified, which in turn speeds up each response. This feature is managed using the Query-Cache-Control header and specifying the max-age in milliseconds in the header response of the handleRequest function. The function response is stored in memory. The cache is purged each time there is a deployment of an asset or a function.

// get.index.js
export async function handleRequest(req) {
  const db = new Database("example.sql");

  const result = await db.query("SELECT * FROM example WHERE id = ?", [1]);

  return new Response(JSON.stringify({ data: result }), {
    status: 200,
    headers: {
      "Content-Type": "application/json",
      "Query-Cache-Control": "max-age=3600000", // 1 hour
    },
  });
}

The function cache store can be configured using environment variables, the default values are:

QUERY_FUNCTION_CACHE_MAX_CAPACITY = 25 * 1024 * 1024; // 25 MB
QUERY_FUNCTION_CACHE_TIME_TO_IDLE = 3600;             // 1 hour
QUERY_FUNCTION_CACHE_TIME_TO_LIVE = 86400;            // 1 day

These environment variables must be defined before deploying the Query Server.

Usage

Query uses under the hood esbuild to bundle the functions. So, first you have to install esbuild:

npm install esbuild

Or

pnpm install esbuild

To use the functions you have to run the following command:

query function <PATH>

The path is optional. If you don't provide it, it will use the default path functions. You can use the path to point to another folder or a function file.

Example

query function

It will deploy all the functions to the Query Server. A simple cache is implemented to avoid deploying functions that have not changed.

query function another-functions-folder

It will deploy all the functions in the another-functions-folder folder to the Query Server.

query function functions/get.index.js

It will deploy the get.index.js function to the Query Server.

query function functions/get.index.js --delete

It will delete the get.index.js function from the Query Server.