API Setup
-
Install Axios
To manage HTTP requests efficiently, we will use Axios, a widely-used promise-based HTTP client.
Terminal window pnpm add axios -
Install Bun
Bun is a fast JavaScript runtime that will be used for executing server-side code.
Terminal window npm install -g bunFor detailed instructions, refer to the Bun Installation Guide.
-
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.
- Replace
-
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; -
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}`); -
Create an Empty Tokens File
Create an empty JSON file at
src/services/tokens.json
:{} -
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;} -
Consolidate API Calls in a Single File (
apis/index.js
)Create an
index.js
file in thesrc/apis
directory to group and export all your API functions:src/apis/index.js import { getUsers } from "./users";export { getUsers }; -
Start the Local Server with Bun
To run the API server locally, use the following command:
Terminal window bun src/services/server.js