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_AUTH_TOKEN = <AUTH_TOKEN>VITE_DATABASE = <DATABASE>VITE_BASE_URL = <BASE_URL>VITE_FQ_LOCAL_SERVER_PORT = 4466VITE_FQ_LOCAL_SERVER = http://localhost: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, { type AxiosRequestConfig } from "axios";import tokens from "./tokens.json";interface Tokens {[key: string]: string | false;}// Base64 encoding charactersconst base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function toBase64(num: number): string {let result = "";const str = num.toString();for (let i = 0; i < str.length; i++) {const charCode = parseInt(str[i]);result += base64chars[charCode % 64];}return result;}const database = import.meta.env.VITE_DATABASE;const baseUrl = import.meta.env.VITE_BASE_URL;const localServer = import.meta.env.VITE_FQ_LOCAL_SERVER;type HttpMethod = "get" | "post" | "put" | "delete" | "sql";type RequestOptions = {loading?: boolean;body?: {sql: "string";params: [{ [key: string]: string | number }];};key?: string;page?: string;sort?: string;joins?: string;filter?: string;search?: string;nearby?: string;hidden?: string;fields?: string;session?: string;validation?: string;permissions?: string;};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 toBase64(Math.abs(code)).substring(0, 8);}function getKey(method: HttpMethod, url: string, options: RequestOptions) {if (!localServer) throw new Error("localServer is not defined");const _url = localServer + url;const parsed_url = new URL(_url);const pathname = parsed_url.pathname;const request: any = {fields: options?.fields,hidden: options?.hidden,filter: options?.filter,nearby: options?.nearby,collections: options?.joins,permissions: options?.permissions,validation: options?.validation,};request["body_is_array"] = Array.isArray(options.body || {});let tokenStr = pathname;for (const key in request) {if (request[key as keyof typeof request]) {tokenStr += key + ":" + request[key as keyof typeof request];}}const key = method + ":" + pathname + ">" + uniqueKey(tokenStr);return key;}const makeRequest = async (method: HttpMethod, endpoint: string, options: RequestOptions = {}): Promise<unknown> => {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 (nearby) headers.nearby = nearby;if (joins) headers.collections = joins;if (validation) headers.validation = validation;if (permissions) headers.permissions = permissions;const key = getKey(method, endpoint, options);const token = (tokens as Tokens)[key] || false;if (!token) {headers["key"] = key;} else {headers.token = token;}const params: { [key: string]: string | number | boolean | object | undefined } = {page: page,sort: sort,search: search,};try {if (loading) {console.log("Loading started...");}const axiosInstance = axios.create({baseURL: token ? baseUrl : localServer,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;const baseUrl = import.meta.env.VITE_BASE_URL;const hostname = baseUrl.split("://")[1];const basicAuth = "Basic " + import.meta.env.VITE_AUTH_TOKEN;const tokensPath = "src/services/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 dev 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