A type-safe API client for web applications.
Jetflare provides a clean, intuitive interface for HTTP requests, WebSocket connections, Server-Sent Events, file uploads, and intelligent caching - all with full TypeScript support.
- Features
- Installation
- Quick Start
- Defining API Routes
- Making API Calls
- Advanced Features
- Configuration
- Error Handling
- API Reference
- Examples
- Contributing
- License
- Strict Type Safety: Leverage TypeScript for compile-time validation of API requests and responses.
- Intuitive Route Definition: Define your API routes clearly with specified paths, HTTP methods, and payload schemas (body, query, params).
- Built-in Caching: Intelligent caching system with automatic invalidation for GET requests.
- Automatic Retries: Configure automatic retries for failed HTTP requests.
- Request Cancellation: Cancel in-flight requests using
AbortController
. - Upload/Download Progress: Track progress for file uploads and downloads.
- Real-time Communication: First-class support for WebSockets and Server-Sent Events.
- Interceptors: Global request, response, and error interceptors for centralized logic.
- File Uploads: Simplified handling of single and multiple file uploads.
- Configurable: Set default headers, timeouts, and base URLs.
npm install jetflare
# or
yarn add jetflare
# or
pnpm add jetflare
Define your API routes and start making requests.
import { Jetflare, createRoutes } from "jetflare";
// 1. Define your API routes using createRoutes for type inference
export const routes = createRoutes({
GET_users: {
path: "/users",
method: "get",
title: "Get all users",
// Define expected query parameters for type safety
query: {
limit: 0, // number
isActive: false, // boolean
},
},
POST_user: {
path: "/users",
method: "post",
title: "Create new user",
// Define expected request body structure
body: {
name: "string",
email: "string",
age: 0,
},
},
});
// 2. Initialize Jetflare with your API base URL and routes
const jetflare = Jetflare(
"[https://api.example.com](https://api.example.com)",
routes
);
// 3. Make requests and handle responses
async function fetchData() {
try {
// GET request with query parameters
const usersResponse = await jetflare.GET_users({
query: { limit: 5, isActive: true },
});
const usersData = await usersResponse.json();
console.log("All users:", usersData);
// POST request with a request body
const newUserResponse = await jetflare.POST_user({
body: { name: "Alice Smith", email: "[email protected]", age: 28 },
});
const newUserData = await newUserResponse.json();
console.log("New user created:", newUserData);
} catch (error) {
console.error("API call failed:", error);
}
}
fetchData();
Routes are defined as an object where each key is the function name for your API call, and its value is an object defining the endpoint. Use createRoutes
to ensure proper type inference.
import { createRoutes } from "jetflare";
export const routes = createRoutes({
ROUTE_NAME: {
path: string, // The URL path, can include :params
method: "get" | "post" | "put" | "delete" | "patch" | "websocket" | "sse",
title?: string, // Optional, for documentation or debugging
headers?: Record<string, string>, // Optional, default headers for this route
invalidates?: string | string[], // Optional, cache keys to invalidate on success
body?: object, // Optional, type definition for request body (for POST, PUT, PATCH)
query?: object, // Optional, type definition for URL query parameters
params?: object, // Optional, type definition for URL path parameters (e.g., :id)
},
// ... more route definitions
});
export const routes = createRoutes({
// Basic GET request with potential query parameters
GET_products: {
path: "/products",
method: "get",
query: {
category: "string",
minPrice: 0, // Example: number type
inStock: false, // Example: boolean type
},
title: "Get all products",
},
// GET request with path parameters
GET_product_by_id: {
path: "/products/:id",
method: "get",
params: {
id: "string", // Example: string type for path parameter
},
title: "Get product by ID",
},
// POST request with a request body, invalidates 'GET_products' cache on success
CREATE_product: {
path: "/products",
method: "post",
body: {
name: "string",
price: 0,
description: "string",
},
invalidates: ["GET_products"], // Invalidate cache for 'GET_products'
title: "Create a new product",
},
// PUT request with path parameters and body, invalidates specific product cache
UPDATE_product: {
path: "/products/:id",
method: "put",
params: {
id: "string",
},
body: {
price: 0,
},
invalidates: ["GET_products", "GET_product_by_id"], // Invalidate multiple caches
title: "Update a product",
},
// DELETE request with path parameters
DELETE_product: {
path: "/products/:id",
method: "delete",
params: {
id: "string",
},
invalidates: ["GET_products"], // Invalidate 'GET_products' cache
title: "Delete a product",
},
// Example for file upload (handled via payload.body containing File objects)
UPLOAD_product_image: {
path: "/products/:id/image",
method: "post",
params: {
id: "string",
},
body: {
imageFile: {} as File, // Explicitly define as File or File[]
altText: "string",
},
title: "Upload product image",
},
});
Define a WebSocket route by setting the method
to "websocket"
.
export const routes = createRoutes({
WS_chat: {
path: "/chat",
method: "websocket",
title: "Real-time chat",
},
});
Define an SSE route by setting the method
to "sse"
.
export const routes = createRoutes({
SSE_notifications: {
path: "/notifications",
method: "sse",
query: {
userId: "string",
},
title: "Event stream for notifications",
},
});
Once your jetflare
instance is initialized, you can call your API endpoints directly using the route names defined in your routes
object.
// Example: jetflare.GET_users defined with query: { limit: 0, isActive: false }
const usersResponse = await jetflare.GET_users({
query: {
limit: 10,
isActive: true,
},
});
const usersData = await usersResponse.json();
console.log("Users:", usersData);
// Example: jetflare.GET_product_by_id defined with params: { id: "string" }
const productResponse = await jetflare.GET_product_by_id({
params: { id: "product_123" },
});
const productData = await productResponse.json();
console.log("Product:", productData);
// Example: jetflare.CREATE_product defined with body: { name: "string", price: 0 }
const createResponse = await jetflare.CREATE_product({
body: {
name: "New Widget",
price: 29.99,
description: "A fantastic new product.",
},
});
const newProduct = await createResponse.json();
console.log("Created product:", newProduct);
// Example: jetflare.UPDATE_product defined with params: { id: "string" }, body: { price: 0 }
const updateResponse = await jetflare.UPDATE_product({
params: { id: "product_456" },
body: {
price: 35.5,
},
});
const updatedProduct = await updateResponse.json();
console.log("Updated product:", updatedProduct);
// Example: jetflare.DELETE_product defined with params: { id: "string" }
const deleteResponse = await jetflare.DELETE_product({
params: { id: "product_789" },
});
if (deleteResponse.ok) {
console.log("Product deleted successfully.");
}
Each request accepts an optional payload
object to configure behavior for that specific call:
interface ApiFunctionPayload {
body?: any; // Request body for POST, PUT, PATCH
query?: Record<string, any>; // URL query parameters
params?: Record<string, any>; // URL path parameters (e.g., :id)
headers?: Record<string, string>; // Additional request headers
cache?: boolean | { ttl?: number; key?: string }; // Caching options
timeout?: number; // Request timeout in milliseconds
retry?: boolean | { attempts?: number; delay?: number }; // Retry configuration
onUploadProgress?: (progress: {
loaded: number;
total: number;
percentage: number;
}) => void; // Upload progress callback
onDownloadProgress?: (progress: {
loaded: number;
total: number;
percentage: number;
}) => void; // Download progress callback
signal?: AbortSignal; // For manual request cancellation
}
Jetflare returns an enhanced JetResponse
object, which wraps the standard Response
object and adds useful properties:
const response = await jetflare.GET_users();
// Standard Response properties
console.log(response.ok); // boolean: True if status is 200-299
console.log(response.status); // number: HTTP status code
console.log(response.statusText); // string: HTTP status message
console.log(response.headers); // Headers object
console.log(response.url); // string: Final URL of the response
// Enhanced properties
console.log(response.cached); // boolean: Was this response eligible for caching?
console.log(response.fromCache); // boolean: Did this response come from the cache?
// Response body methods (asynchronous)
const json = await response.json(); // Parses response as JSON
const text = await response.text(); // Parses response as plain text
const blob = await response.blob(); // Parses response as Blob
const buffer = await response.arrayBuffer(); // Parses response as ArrayBuffer
const formData = await response.formData(); // Parses response as FormData
Jetflare simplifies file uploads by automatically converting File
or FileList
objects within your payload.body
into FormData
. Progress tracking is available via onUploadProgress
and onDownloadProgress
.
// Example: jetflare.UPLOAD_product_image defined with body: { imageFile: {} as File, altText: "string" }
// Get a single file from an input element
const fileInput = document.getElementById("imageFile") as HTMLInputElement;
const selectedFile = fileInput.files?.[0];
if (selectedFile) {
try {
const uploadResponse = await jetflare.UPLOAD_product_image({
params: { id: "product_123" },
body: {
imageFile: selectedFile, // Pass the File object directly
altText: "Front view of the product",
},
onUploadProgress: (progress) => {
console.log(`Upload Progress: ${progress.percentage}%`);
// Update a UI progress bar here
},
onDownloadProgress: (progress) => {
console.log(`Download Progress: ${progress.percentage}%`);
},
});
const uploadResult = await uploadResponse.json();
console.log("File upload successful:", uploadResult);
} catch (error) {
console.error("File upload failed:", error);
}
}
// Example for multiple files if your route body supports an array of Files
// Route definition might look like:
// UPLOAD_documents: { path: "/documents", method: "post", body: { files: [] as File[], category: "string" } }
const multipleFilesInput = document.getElementById(
"multipleFiles"
) as HTMLInputElement;
const filesToUpload = multipleFilesInput.files
? Array.from(multipleFilesInput.files)
: [];
if (filesToUpload.length > 0) {
try {
const multiUploadResponse = await jetflare.UPLOAD_documents({
body: {
files: filesToUpload, // Pass an array of File objects
category: "manuals",
},
onUploadProgress: (progress) => {
console.log(`Multi-file Upload Progress: ${progress.percentage}%`);
},
});
const multiUploadResult = await multiUploadResponse.json();
console.log("Multiple files upload successful:", multiUploadResult);
} catch (error) {
console.error("Multiple files upload failed:", error);
}
}
For WebSocket routes, the generated function returns an object with methods to manage the connection, including direct access to the underlying WebSocket
instance.
// Example: jetflare.WS_chat defined with method: "websocket"
const chatService = jetflare.WS_chat();
// Connect to the WebSocket
chatService.connect();
// Get the raw WebSocket instance for advanced usage
const rawSocket = chatService.socket(); // Access the WebSocket object directly
if (rawSocket) {
rawSocket.onopen = () => console.log("WebSocket connected directly!");
rawSocket.onmessage = (event) =>
console.log("Direct WS message:", event.data);
}
// Use Jetflare's convenience methods for event listening
chatService.on("message", (event) => {
console.log("Received message:", JSON.parse(event.data));
});
chatService.on("open", () => {
console.log("WebSocket connection opened!");
chatService.send({ type: "greeting", message: "Hello from Jetflare!" });
});
chatService.on("close", () => {
console.log("WebSocket connection closed.");
});
chatService.on("error", (error) => {
console.error("WebSocket error:", error);
});
// Send data
chatService.send("Plain text message");
chatService.send({ action: "ping", timestamp: Date.now() });
// Close the connection
// chatService.close();
For SSE routes, the generated function returns an object with methods to manage the connection, including direct access to the underlying EventSource
instance.
// Example: jetflare.SSE_notifications defined with method: "sse"
const notificationService = jetflare.SSE_notifications({
query: { userId: "user_abc" },
});
// Connect to the SSE stream
notificationService.connect();
// Get the raw EventSource instance for advanced usage
const rawEventSource = notificationService.event(); // Access the EventSource object directly
if (rawEventSource) {
rawEventSource.onopen = () => console.log("SSE connection opened directly!");
rawEventSource.onerror = (error) => console.error("Direct SSE error:", error);
}
// Use Jetflare's convenience methods for event listening
notificationService.on("message", (event) => {
console.log("New message:", event.data);
});
notificationService.on("user-update", (event) => {
console.log("User updated:", JSON.parse(event.data));
});
// Close the connection
// notificationService.close();
All HTTP requests can accept a payload
object to customize individual request behavior:
const response = await jetflare.GET_users({
headers: { "X-API-Key": "my-secret-key" }, // Custom headers for this request
cache: { ttl: 60000, key: "recent_users" }, // Cache for 1 minute with a specific key
timeout: 8000, // Override global timeout to 8 seconds for this request
retry: { attempts: 3, delay: 500 }, // Retry up to 3 times with 500ms delay between attempts
// onUploadProgress and onDownloadProgress as shown in File Uploads section
// signal for manual cancellation (see Request Cancellation)
});
Jetflare includes an intelligent caching system primarily for GET
requests. It automatically caches responses and can invalidate related cache entries when mutation (POST, PUT, PATCH, DELETE) requests succeed.
// Define caching directly in route configuration (static cache key)
export const routes = createRoutes({
GET_users: {
path: "/users",
method: "get",
cache: { ttl: 300000, key: "all-users-list" }, // Cache for 5 minutes
title: "Get all users with static cache",
},
GET_user_by_id: {
path: "/users/:id",
method: "get",
// Dynamic cache key using path parameters for per-user caching
cache: { ttl: 60000, key: "user-:id" },
title: "Get user by ID with dynamic cache",
},
});
// Override cache behavior per-request
const responseA = await jetflare.GET_users({
cache: { ttl: 30000 }, // Cache this specific request for 30 seconds
});
const responseB = await jetflare.GET_users({
cache: false, // Disable caching for this specific request
});
Define invalidates
in your route configuration to automatically clear relevant cache entries upon successful mutation. Supports specific keys and patterns.
export const routes = createRoutes({
// ...
POST_user: {
path: "/users",
method: "post",
invalidates: ["all-users-list"], // Invalidate the 'all-users-list' cache
title: "Create new user (invalidates user list cache)",
},
PUT_user: {
path: "/users/:id",
method: "put",
// Invalidate the specific user and the overall list
invalidates: ["user-:id", "all-users-list"],
title: "Update user (invalidates user-specific and list cache)",
},
});
You can directly interact with the cache manager through jetflare.cache
.
// Clear all cached entries
jetflare.cache.clear();
// Invalidate specific cache keys or patterns
jetflare.cache.invalidate("all-users-list");
jetflare.cache.invalidate(["user-123", "posts-*"]); // Supports globs with '*'
// Get a cached entry directly
const cachedUsers = jetflare.cache.get("all-users-list");
// Manually set a cache entry
jetflare.cache.set("custom-data", { value: "example" }, 300000); // 5 minutes TTL
Configure automatic retries for failed HTTP requests using the retry
option in your payload.
const response = await jetflare.GET_users({
retry: {
attempts: 5, // Attempt the request up to 5 times (1 initial + 4 retries)
delay: 2000, // Wait 2 seconds between retry attempts
},
timeout: 5000, // Each attempt has its own 5-second timeout
});
If retry
is set to true
, it will default to 1 attempt and 1000ms delay.
Jetflare supports request cancellation using the standard AbortController
API.
const controller = new AbortController();
async function fetchWithCancellation() {
try {
const usersPromise = jetflare.GET_users({ signal: controller.signal });
// Simulate cancelling after some time
setTimeout(() => {
console.log("Aborting request...");
controller.abort();
}, 100);
const response = await usersPromise;
console.log("Request completed:", await response.json());
} catch (error) {
if (error.name === "AbortError") {
console.log("Request successfully aborted.");
} else {
console.error("Request failed:", error);
}
}
}
fetchWithCancellation();
Interceptors allow you to hook into the request and response lifecycle globally to perform actions like logging, authentication, or error handling.
Executed before a request is sent. They receive the payload
configuration and can modify it.
// Add authentication token to all requests
jetflare.interceptors.request.add((config) => {
const token = localStorage.getItem("authToken");
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
return config; // Always return the config object
});
// Add request logging
jetflare.interceptors.request.add((config) => {
console.log(
`Sending request: ${config.route.method.toUpperCase()} ${config.route.path}`
);
return config;
});
Executed after a response is received (for both success and error status codes). They receive the JetResponse
object and can modify it.
// Log response status
jetflare.interceptors.response.add((response) => {
console.log(`Received response: ${response.status} ${response.statusText}`);
return response; // Always return the response object
});
// Handle global refresh tokens (if needed)
jetflare.interceptors.response.add(async (response) => {
if (response.status === 401 && !response.url.includes("/refresh-token")) {
// Logic to refresh token, then potentially retry the original request
console.warn("Authentication failed, attempting token refresh...");
// ... call a refresh token endpoint ...
// Note: Retrying original request within interceptor requires careful handling
}
return response;
});
Executed when a request fails (e.g., network error, timeout, or uncaught HTTP error). They receive the Error
object.
// Global error logging and user notification
jetflare.interceptors.error.add((error) => {
console.error("Jetflare API Error:", error.message);
// Example: Display a toast notification
// showToast("An API error occurred: " + error.message, "error");
return error; // Always re-throw or return the error
});
You can set default headers, timeouts, and the base URL globally for all requests made by your Jetflare instance.
// Set default headers for all HTTP requests
jetflare.setDefaultHeaders({
"Content-Type": "application/json",
"X-Client-ID": "my-app-v1",
});
// Set a global timeout for all requests (e.g., 15 seconds)
jetflare.setDefaultTimeout(15000);
// Update the base URL dynamically
jetflare.setBaseURL("https://new-api.example.com/v2");
Jetflare also provides a fluent API for chaining common configuration methods during initialization or later.
import { Jetflare, createRoutes } from "jetflare";
const baseRoutes = createRoutes({
/* ... your routes ... */
});
const jetflare = Jetflare("https://api.example.com", baseRoutes);
Jetflare handles network errors, timeouts, and non-2xx HTTP responses by throwing errors. You should typically wrap your API calls in try...catch
blocks.
try {
const response = await jetflare.GET_users({ timeout: 2000 }); // Example with timeout
if (!response.ok) {
// Handle specific HTTP error responses (e.g., 404, 500)
const errorData = await response.json().catch(() => null);
throw new Error(
`API Error: ${response.status} ${response.statusText} - ${
errorData?.message || "Unknown error"
}`
);
}
const data = await response.json();
console.log("Data fetched:", data);
} catch (error) {
// Catch network errors, timeouts (AbortError), and custom thrown errors
if (error instanceof Error) {
if (error.name === "AbortError") {
console.error(
"Request aborted (timeout or manual cancellation):",
error.message
);
} else {
console.error("Failed to fetch data:", error.message);
}
} else {
console.error("An unexpected error occurred:", error);
}
}
For global error handling, refer to the Error Interceptors section.
Initializes the Jetflare client.
origin
: The base URL for your API (e.g., "https://www.google.com/url?sa=E\&source=gmail\&q=https://api.example.com").routes
: An object defining your API endpoints.
origin: string
: The current base URL of the API client.cache: CacheManager
: Instance of the cache manager for manual cache operations.wsManager: WebSocketManager
: Instance of the WebSocket manager for direct WebSocket control.interceptors
:request: Set<(config: any) => any>
: Add functions to intercept and modify outgoing request configurations.response: Set<(response: JetResponse) => JetResponse>
: Add functions to intercept and modify incoming responses.error: Set<(error: Error) => Error>
: Add functions to intercept and handle errors.
setBaseURL(url: string): void
: Sets a new base URL for the API client.setDefaultHeaders(headers: Record<string, string>): void
: Sets/merges default headers for all subsequent requests.setDefaultTimeout(timeout: number): void
: Sets a global timeout in milliseconds for all requests.withAuth(token: string): API<routes>
: A fluent method to set a Bearer token in default headers.withTimeout(timeout: number): API<routes>
: A fluent method to set the default timeout.withBaseURL(url: string): API<routes>
: A fluent method to set the base URL.
get(key: string): any | null
: Retrieves data from the cache by key.set(key: string, data: any, ttl?: number): void
: Stores data in the cache with an optional time-to-live (TTL) in milliseconds (defaults to 5 minutes).invalidate(pattern: string | string[]): void
: Invalidates cache entries matching a key or pattern(s). Supports*
for wildcards.clear(): void
: Clears all entries from the cache.
The response object returned by HTTP API calls.
interface JetResponse {
ok: boolean; // True if HTTP status is 200-299
status: number; // HTTP status code (e.g., 200, 404, 500)
statusText: string; // HTTP status message (e.g., "OK", "Not Found")
headers: Headers; // Standard Headers object
url: string; // The URL of the response
cached: boolean; // True if the response was eligible for caching
fromCache: boolean; // True if the response was served from cache
json<T>(): Promise<T>; // Parses the response body as JSON
text(): Promise<string>; // Parses the response body as plain text
blob(): Promise<Blob>; // Parses the response body as a Blob
arrayBuffer(): Promise<ArrayBuffer>; // Parses the response body as an ArrayBuffer
formData(): Promise<FormData>; // Parses the response body as FormData
}
The configuration object passed to individual HTTP API functions.
interface ApiFunctionPayload {
body?: any; // Request body for POST, PUT, PATCH. Supports objects (JSON) or FormData for files.
query?: Record<string, any>; // URL query parameters (e.g., ?limit=10)
params?: Record<string, any>; // URL path parameters (e.g., /users/:id -> { id: '123' })
headers?: Record<string, string>; // Additional headers for this specific request
cache?: boolean | { ttl?: number; key?: string }; // Override caching behavior for this request
timeout?: number; // Override global timeout for this request in milliseconds
retry?: boolean | { attempts?: number; delay?: number }; // Configure retries for this request
onUploadProgress?: (progress: {
// Callback for tracking upload progress
loaded: number;
total: number;
percentage: number;
}) => void;
onDownloadProgress?: (progress: {
// Callback for tracking download progress
loaded: number;
total: number;
percentage: number;
}) => void;
signal?: AbortSignal; // An AbortSignal to manually cancel the request
}
For more comprehensive examples, see the Examples directory in the GitHub repository.
This example demonstrates integrating multiple Jetflare features for a user management service.
import { Jetflare, createRoutes } from "jetflare";
// Define user-related API routes
const userRoutes = createRoutes({
GET_all_users: {
path: "/users",
method: "get",
cache: { ttl: 300000, key: "all-users-list" }, // Cache for 5 mins
title: "Retrieve all users",
},
GET_user_by_id: {
path: "/users/:id",
method: "get",
params: { id: "string" },
cache: { ttl: 60000, key: "user-:id" }, // Cache per user for 1 min
title: "Retrieve user by ID",
},
POST_new_user: {
path: "/users",
method: "post",
body: { name: "string", email: "string", password: "string" },
invalidates: ["all-users-list"], // Invalidate user list cache
title: "Create a new user",
},
PUT_update_user: {
path: "/users/:id",
method: "put",
params: { id: "string" },
body: { name: "string", email: "string" },
invalidates: ["all-users-list", "user-:id"], // Invalidate user list and specific user cache
title: "Update an existing user",
},
DELETE_user: {
path: "/users/:id",
method: "delete",
params: { id: "string" },
invalidates: ["all-users-list", "user-:id"], // Invalidate user list and specific user cache
title: "Delete a user",
},
UPLOAD_user_avatar: {
path: "/users/:id/avatar",
method: "post",
params: { id: "string" },
body: { avatar: {} as File, description: "string" }, // File upload with additional data
invalidates: ["user-:id"], // Invalidate specific user cache
title: "Upload user avatar",
},
});
// Initialize Jetflare instance with authentication and a global timeout
const jetflareApi = Jetflare(
"[https://api.yourapp.com](https://api.yourapp.com)",
userRoutes
)
.withAuth("your-static-jwt-token-here") // Or fetch dynamically and set via interceptor
.withTimeout(20000); // 20 seconds global timeout
// Define a service class for user-related operations
class UserService {
async getAllUsers(page: number = 1, limit: number = 10) {
const response = await jetflareApi.GET_all_users({
query: { page, limit },
});
return response.json();
}
async getUserById(id: string) {
const response = await jetflareApi.GET_user_by_id({ params: { id } });
return response.json();
}
async createUser(data: { name: string; email: string; password?: string }) {
const response = await jetflareApi.POST_new_user({ body: data });
return response.json();
}
async updateUser(id: string, updates: { name?: string; email?: string }) {
const response = await jetflareApi.PUT_update_user({
params: { id },
body: updates,
});
return response.json();
}
async deleteUser(id: string) {
const response = await jetflareApi.DELETE_user({ params: { id } });
if (!response.ok) {
throw new Error(`Failed to delete user: ${response.statusText}`);
}
return response.ok;
}
async uploadUserAvatar(
userId: string,
avatarFile: File,
description: string,
onProgress?: (p: {
loaded: number;
total: number;
percentage: number;
}) => void
) {
const response = await jetflareApi.UPLOAD_user_avatar({
params: { id: userId },
body: { avatar: avatarFile, description },
onUploadProgress: onProgress, // Pass the progress callback
});
return response.json();
}
}
export const userService = new UserService();
// Example Usage (in a React component or utility)
async function runUserExamples() {
try {
// Fetch all users
const users = await userService.getAllUsers(1, 5);
console.log("Fetched users:", users);
// Fetch a specific user
const singleUser = await userService.getUserById("user-abc-123");
console.log("Fetched single user:", singleUser);
// Create a new user
const newUser = await userService.createUser({
name: "Test User",
email: "[email protected]",
password: "securepassword",
});
console.log("Created user:", newUser);
// Update a user
const updatedUser = await userService.updateUser(newUser.id, {
email: "[email protected]",
});
console.log("Updated user:", updatedUser);
// Upload an avatar for a user
const dummyFile = new File(["dummy content"], "avatar.png", {
type: "image/png",
});
const uploadResult = await userService.uploadUserAvatar(
newUser.id,
dummyFile,
"Profile picture",
(p) => {
console.log(`Avatar upload: ${p.percentage}%`);
}
);
console.log("Avatar upload result:", uploadResult);
// Delete a user
const deleted = await userService.deleteUser(newUser.id);
console.log("User deleted:", deleted);
} catch (error) {
console.error("User service example failed:", error);
}
}
runUserExamples();
Demonstrates how to use WebSockets for a real-time chat application.
import { Jetflare, createRoutes } from "jetflare";
const chatRoutes = createRoutes({
WS_chat_room: {
path: "/chat/:roomId",
method: "websocket",
title: "Real-time chat room connection",
},
POST_chat_message: {
path: "/chat/:roomId/messages",
method: "post",
params: { roomId: "string" },
body: { message: "string", senderId: "string" },
title: "Send chat message (via REST for history)",
},
});
const jetflareChat = Jetflare("ws://localhost:8080", chatRoutes); // Use ws:// for WebSocket
class ChatService {
private wsConnection: ReturnType<typeof jetflareChat.WS_chat_room> | null =
null;
private currentRoomId: string | null = null;
joinRoom(roomId: string, onMessageReceived: (message: any) => void) {
if (this.wsConnection && this.currentRoomId === roomId) {
console.log(`Already connected to room ${roomId}`);
return;
}
this.leaveRoom(); // Close any existing connection
this.currentRoomId = roomId;
this.wsConnection = jetflareChat.WS_chat_room({ params: { roomId } });
// Connect to the WebSocket
this.wsConnection.connect();
// Set up event handlers
this.wsConnection.on("open", () => {
console.log(`Connected to room ${roomId}`);
// Send a "join" message or other initial handshake
this.wsConnection?.send(JSON.stringify({ type: "join", roomId }));
});
this.wsConnection.on("message", (event: MessageEvent) => {
try {
const data = JSON.parse(event.data);
onMessageReceived(data); // Callback for UI updates
} catch (e) {
console.error("Failed to parse WS message:", e);
}
});
this.wsConnection.on("close", () => {
console.log("Disconnected from chat.");
this.currentRoomId = null;
});
this.wsConnection.on("error", (error: Event) => {
console.error("WebSocket error:", error);
});
}
sendMessage(message: string, senderId: string) {
if (this.wsConnection && this.currentRoomId) {
this.wsConnection.send(
JSON.stringify({
type: "chat",
roomId: this.currentRoomId,
senderId,
content: message,
timestamp: new Date().toISOString(),
})
);
// Also send via REST for message history persistence (optional)
jetflareChat
.POST_chat_message({
params: { roomId: this.currentRoomId },
body: { message, senderId },
})
.catch((err) => console.error("Failed to post message via REST:", err));
} else {
console.warn("Not connected to a chat room.");
}
}
leaveRoom() {
if (this.wsConnection) {
this.wsConnection.close();
this.wsConnection = null;
console.log("Left current chat room.");
}
}
}
const chatService = new ChatService();
// Example Usage:
chatService.joinRoom("general-chat", (message) => {
console.log("New chat message:", message);
// Update your UI with the new message
});
setTimeout(() => {
chatService.sendMessage("Hello everyone!", "user-456");
}, 2000);
setTimeout(() => {
chatService.leaveRoom();
}, 10000);
We welcome contributions to Jetflare! Please read our contributing guidelines for details on how to submit pull requests, report bugs, and suggest features.
Jetflare is licensed under the MIT License. See the LICENSE file for more details.