Skip to content

API Setup

  1. Install Axios

    To manage HTTP requests efficiently, we will use Axios, a widely-used promise-based HTTP client.

    Terminal window
    pnpm add axios
  2. Install Bun

    Bun is a fast JavaScript runtime that will be used for executing server-side code.

    Terminal window
    npm install -g bun

    For detailed instructions, refer to the Bun Installation Guide.

  3. Configure Environment Variables

    In the root directory of your project, create a .env.local file and define the following variables:

    .env.local
    VITE_DATABASE = <DATABASE>
    VITE_AUTH = <AUTH>
    VITE_BASE_URL = <BASE_URL>
    VITE_FQ_LOCAL_SERVER_PORT = 4466
    • Replace <DATABASE> with your database name.
    • Replace <AUTH> with your authentication credentials.
    • Replace <BASE_URL> with the base URL of your API server.
  4. API Service Configuration (src/services/Api.ts)

    Create or update the src/services/Api.ts file with the following content to set up a flexible API service:

    src/services/Api.ts
    import axios, { AxiosRequestConfig } from "axios";
    import { Buffer } from "buffer";
    export const _DATABASE = import.meta.env.VITE_DATABASE;
    export const _BASE_URL = import.meta.env.VITE_BASE_URL;
    const local_host = `http://localhost:${import.meta.env.VITE_FQ_LOCAL_SERVER_PORT}`;
    import tokens from "./tokens.json";
    interface Tokens {
    [key: string]: string;
    }
    const typedTokens: Tokens = tokens as Tokens;
    type HttpMethod = "get" | "post" | "put" | "delete" | "sql";
    type RequestOptions = {
    loading?: boolean;
    body?: {
    sql: string;
    params: [{ [key: string]: string | number }];
    };
    key?: Record<string, string | any>;
    page?: Record<string, string | number>;
    sort?: Record<string, string | number>;
    joins?: Record<string, string | number>;
    filter?: Record<string, string | number>;
    search?: Record<string, string | number>;
    nearby?: Record<string, string | number>;
    hidden?: Record<string, string | number>;
    fields?: Record<string, string | number>;
    session?: Record<string, string | number>;
    validation?: Record<string, string | number>;
    permissions?: Record<string, string | number>;
    };
    function uniqueKey(input: string) {
    let code = input.charCodeAt(0);
    for (let i = 0; i < input.length; i++) {
    const char = input.charCodeAt(i);
    code = (code << 5) - code + char;
    code &= code;
    }
    return Buffer.from(code.toString()).toString("base64").substring(0, 8);
    }
    function isSQLQuery(method: HttpMethod | "SQL", body: any) {
    if (method === "SQL") {
    if (!body) return false;
    if (Array.isArray(body)) {
    return body.map(b => b.sql).every(b => typeof b === "string");
    }
    return typeof body.sql === "string";
    }
    return false;
    }
    function getSQLQuery(body: any) {
    if (Array.isArray(body)) {
    if (body.length === 1) {
    return JSON.stringify(body[0].sql);
    }
    return JSON.stringify(body.map(b => b.sql));
    }
    return JSON.stringify(body.sql);
    }
    function getKey(method: HttpMethod, url: string, options: RequestOptions): string {
    if (!local_host) throw new Error("local_host is not defined");
    const _url = local_host + url;
    const parsed_url = new URL(_url);
    const pathname = "/" + parsed_url.pathname.split("/")[1];
    const request = {
    fields: options.fields,
    hidden: options.hidden,
    filter: options.filter,
    nearby: options.nearby,
    collections: options.joins,
    permissions: options.permissions,
    validation: options.validation,
    body_is_array: !isSQLQuery(method, options.body) ? Array.isArray(options.body || {}) : "",
    sql_query: isSQLQuery(method, options.body) ? getSQLQuery(options.body) : "",
    };
    let tokenStr = pathname;
    for (let key in request) {
    if (request[key as keyof typeof request]) {
    tokenStr += key + ":" + request[key as keyof typeof request];
    }
    }
    return method + ":" + pathname + ">" + uniqueKey(tokenStr);
    }
    const makeRequest = async (method: HttpMethod, endpoint: string, options: RequestOptions = {}): Promise<any> => {
    const {
    body,
    page,
    sort,
    joins,
    hidden,
    fields,
    filter,
    search,
    nearby,
    session,
    validation,
    permissions,
    loading = true,
    } = options;
    const headers: any = {};
    if (hidden) headers.hidden = hidden;
    if (filter) headers.filter = filter;
    if (fields) headers.fields = fields;
    if (session) headers.session = session;
    if (joins) headers.collections = joins;
    if (validation) headers.validation = validation;
    if (permissions) headers.permissions = permissions;
    if (nearby) headers.nearby = nearby;
    const key = getKey(method, endpoint, options);
    const token = typedTokens[key] || false;
    if (!token) {
    headers["key"] = key;
    } else {
    headers.token = token;
    }
    const params: any = {
    page: page,
    sort: sort,
    search: search,
    };
    try {
    if (loading) {
    console.log("Loading started...");
    }
    const axiosInstance = axios.create({
    baseURL: token ? _BASE_URL : local_host,
    headers: { app: _DATABASE },
    });
    const requestConfig: AxiosRequestConfig = {
    method,
    params,
    headers,
    data: body,
    url: endpoint,
    };
    const response = await axiosInstance(requestConfig);
    return response.data;
    } catch (error: any) {
    console.error(`${method.toUpperCase()} Error:`, error.message);
    throw error;
    } finally {
    if (loading) {
    console.log("Loading completed.");
    }
    }
    };
    const Api = {
    get: async (endpoint: string, options?: RequestOptions): Promise<any> => makeRequest("get", endpoint, options),
    put: async (endpoint: string, options?: RequestOptions): Promise<any> => makeRequest("put", endpoint, options),
    post: async (endpoint: string, options?: RequestOptions): Promise<any> => makeRequest("post", endpoint, options),
    delete: async (endpoint: string, options?: RequestOptions): Promise<any> => makeRequest("delete", endpoint, options),
    sql: async (endpoint: string, options?: RequestOptions): Promise<any> =>
    makeRequest("post", `/sql-${endpoint.replace("/", "")}`, options),
    };
    export default Api;
  5. Set Up API Server (src/services/server.js)

    Create or modify the src/services/server.js file to configure the local API server using Bun:

    src/services/server.js
    import { serve, file, write } from "bun";
    const port = import.meta.env.VITE_FQ_LOCAL_SERVER_PORT || 4466;
    const hostname = import.meta.env.VITE_BASE_URL;
    const basicAuth = import.meta.env.VITE_AUTH;
    const tokensPath = "./tokens.json";
    const CORS_HEADERS = {
    headers: {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "*",
    "Access-Control-Allow-Headers": "*",
    },
    };
    serve({
    port,
    async fetch(req) {
    if (req.method === "OPTIONS") {
    return new Response("Departed", CORS_HEADERS);
    }
    const url = new URL(req.url);
    const method = req.method;
    const bodyText = await req.text();
    const tokensFile = file(tokensPath);
    const tokens = await tokensFile.json();
    const key = req.headers.get("key");
    url.port = 443;
    url.protocol = "https:";
    url.hostname = hostname;
    req.headers.delete("host");
    req.headers.set("Accept-Encoding", "br");
    req.headers.append("Authorization", basicAuth);
    const response = await fetch(url, {
    method: method,
    body: bodyText,
    headers: req.headers,
    });
    const body = await response.json();
    if (key && body?.token) {
    tokens[key] = body.token;
    await write(tokensPath, JSON.stringify(tokens, null, 2));
    }
    return new Response(JSON.stringify(body), CORS_HEADERS);
    },
    });
    console.log(`FrontQL development server is running on http://localhost:${port}`);
  6. Create an Empty Tokens File

    Create an empty JSON file at src/services/tokens.json:

    {}
  7. Organize API Calls

    To maintain a clean project structure, it is recommended to organize your API calls into separate modules. For example, for the ‘users’ related API calls, create a src/apis/users.js file:

    src/apis/users.js
    import Api from "services/Api";
    export async function getUsers() {
    const response = await Api.get("/users");
    return response;
    }
  8. Consolidate API Calls in a Single File (apis/index.js)

    Create an index.js file in the src/apis directory to group and export all your API functions:

    src/apis/index.js
    import { getUsers } from "./users";
    export { getUsers };
  9. Start the Local Server with Bun

    To run the API server locally, use the following command:

    Terminal window
    bun src/services/server.js