Effortless Function as a Service: A Simple Guide to Implementing it with Query
Tags: javascript, sqlite, rust, litefs
Published: 2023-12-21 10:59:50

[Query](https://github.com/gc-victor/query) is a Rust server that provides an [API](https://github.com/gc-victor/query?tab=readme-ov-file#apis), [CLI](https://github.com/gc-victor/query?tab=readme-ov-file#cli), and authentication for remote interaction with SQLite databases. It has introduced ["functions"](https://github.com/gc-victor/query?tab=readme-ov-file#function) to enhance your product with hosted JavaScript code accessing remote [SQLite](https://www.sqlite.org/index.html) databases.

Let's explore a practical example of a Query function:

```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",
        },
    });
}
```

In this example, the function fetches data from a database table and responds with a JSON object containing the result.

A Query function follows a specific format. It exports an asynchronous function named `handleRequest` that takes a [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) and returns a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).

A connection to a database is required to interact with databases using the Query function. It can be achieved by utilizing the `Database` constructor.

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

The `Database` constructor takes the name of the database. Once connected, it provides two essential methods:

- `query`: Reads data from the database.
- `execute`: Writes data to the database.

Both `query` and `execute` can accept parameters as an array or an object with placeholders like `:AAA`, `$AAA`, or `@AAA`.

Remember, when working with Query and [LiteFS proxy](https://fly.io/docs/litefs/config/#http-proxy-server), use `GET` to read data and `DELETE|POST|PUT|PATCH` to write data.

Query follows a specific folder structure to organize functions and define routes. Here's an example:

```sh
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"
...
```

The prefix determines the method used in a file `(delete|get|patch|post|put).*`. The remaining part of the file name defines the final segment of the route. For instance, if the file name ends with `index`, it represents the root of the route.

If you're looking to deploy and replicate your databases across the globe quickly, [Fly.io](fly.io/) provides a seamless solution to run a Query Server in just a few minutes.

To incorporate Query into your deployment, your [Dockerfile needs to include the Query Server](https://github.com/gc-victor/query?tab=readme-ov-file#run-a-query-server-on-flyio). Consider using a multistage Dockerfile, with the final stage being a compatible `x86_64-unknown-linux-gnu` image, such as `debian:<suite>-slim`. Refer to the [LiteFS documentation](https://fly.io/docs/litefs/speedrun/) for critical information on this essential system component.

Here is a sample Dockerfile for your Query Server:

```Dockerfile
FROM debian:12-slim

ADD litefs.yml /etc/litefs.yml
COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    ca-certificates \
    sqlite3 \
    fuse3 \
    curl

# Download and install Query Server
RUN curl --proto '=https' --tlsv1.2 -LsSf https://github.com/gc-victor/query/releases/latest/download/query-server-installer.sh | sh

EXPOSE 3000

CMD ["litefs", "mount"]
```

Additionally, to kickstart the server, add this command to the [litefs.yml](https://fly.io/docs/litefs/config/) configuration file:

```yml
...
exec:
  - cmd: "~/.cargo/bin/query-server"
...
```

With these components in place, your Query Server will be ready to deploy on Fly, providing a robust and globally accessible solution.

Let's install the toolset to deploy the functions.

We have to install the [Query CLI](https://github.com/gc-victor/query?tab=readme-ov-file#cli). It is a powerful tool that lets you manage your remote SQLite databases with ease and safety.

Form macOS and Linux (not NixOS, Alpine, or Asahi):

```sh
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/gc-victor/query/releases/latest/download/query-installer.sh | sh
```

The functions will bundle using [esbuild](https://esbuild.github.io). For that, it is required to install esbuild globally:

```sh
npm install --global esbuild
```

Finally, to deploy the functions, run:

```sh
query function
```

With their structured approach and database integration, [Query functions](https://github.com/gc-victor/query?tab=readme-ov-file#function) offer a robust foundation for building scalable and efficient [Function as a Service (FaaS)](https://en.wikipedia.org/wiki/Function_as_a_service).