@ticatec/restful_service_api
v0.5.0
Published
A lightweight TypeScript RESTful API client for browsers with error handling. (ESM only, v0.5.0+)
Readme
@ticatec/restful_service_api
A lightweight TypeScript RESTful API client for browsers with comprehensive error handling and interceptor support.
中文版 | English
Features
- 🚀 Lightweight: Zero dependencies, built for modern browsers
- 🔧 TypeScript Support: Full type safety with TypeScript definitions
- 🛡️ Error Handling: Built-in error handling with custom ApiError class
- ⚡ Interceptors: Pre and post request interceptors for authentication and data processing
- 🎯 Flexible: Support for custom headers, timeouts, and data processors
- 📁 File Operations: Dedicated interface for file upload/download operations
- 🌐 Browser-First: Designed specifically for frontend applications
- ✨ PATCH Support: Full support for HTTP PATCH method for partial updates
⚠️ Breaking Changes in v0.5.0
Starting from version 0.5.0, this package has migrated to ESM (ECMAScript Modules) format.
What This Means for You
- Your project must use ESM format (have
"type": "module"in package.json or use.mjsextension) require()is no longer supported. Useimportstatements instead
Migration Guide
If you're upgrading from a version prior to 0.5.0:
Before (CommonJS):
const RestService = require('@ticatec/restful_service_api');After (ESM):
import RestService from '@ticatec/restful_service_api';For CommonJS projects, you may need to use dynamic import:
const { default: RestService } = await import('@ticatec/restful_service_api');Installation
npm install @ticatec/restful_service_apiQuick Start
import RestService, { FileService } from '@ticatec/restful_service_api';
// Your implementation of RestService interface
class MyApiClient implements RestService {
async get(url: string, params?: any, dataProcessor?: DataProcessor) {
// Implementation here
}
async post(url: string, data?: any, options?: RestfulOptions) {
// Implementation here
}
async patch(url: string, data?: any, options?: RestfulOptions) {
// Implementation here
}
// ... other methods
}
// Your implementation of FileService interface
class MyFileClient implements FileService {
async upload(url: string, params: any, file: File, fileKey?: string, dataProcessor?: DataProcessor) {
// Implementation here
}
async asyncUpload(url: string, params: any, file: File, callback: UploadCallback, fileKey?: string) {
// Implementation here
}
async download(url: string, filename: string, params: any, method?: string, formData?: any) {
// Implementation here
}
}
const api = new MyApiClient();
const fileApi = new MyFileClient();
// Make REST requests
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John Doe' });
const partialUpdate = await api.patch('/users/1', { status: 'active' });
// File operations
const result = await fileApi.upload('/upload', { userId: 123 }, file);API Reference
RestService Interface
The main interface for standard HTTP REST operations:
interface RestService {
get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any>;
post(url: string, data?: any, options?: RestfulOptions): Promise<any>;
put(url: string, data?: any, options?: RestfulOptions): Promise<any>;
patch(url: string, data?: any, options?: RestfulOptions): Promise<any>;
del(url: string, data?: any, options?: RestfulOptions): Promise<any>;
}FileService Interface
Dedicated interface for file upload and download operations:
interface FileService {
upload(url: string, params: any, file: File, fileKey?: string, dataProcessor?: DataProcessor): Promise<any>;
asyncUpload(url: string, params: any, file: File, callback: UploadCallback, fileKey?: string): Promise<UploadProgress>;
download(url: string, filename: string, params: any, method?: string, formData?: any): Promise<any>;
}RestService Methods
get(url, params?, dataProcessor?)
Performs HTTP GET request to retrieve a resource.
- url: The endpoint URL
- params: Query parameters (optional)
- dataProcessor: Function to process response data (optional)
post(url, data?, options?)
Performs HTTP POST request to create a new resource.
- url: The endpoint URL
- data: Request payload (optional)
- options: Optional configuration object containing:
- params: Query parameters (optional)
- contentType: Content-Type header (optional, defaults to application/json)
- dataProcessor: Function to process response data (optional)
put(url, data?, options?)
Performs HTTP PUT request to update an entire resource.
- url: The endpoint URL
- data: Request payload (optional)
- options: Optional configuration object containing:
- params: Query parameters (optional)
- contentType: Content-Type header (optional, defaults to application/json)
- dataProcessor: Function to process response data (optional)
patch(url, data?, options?)
Performs HTTP PATCH request to partially update a resource.
- url: The endpoint URL
- data: Request payload with fields to update (optional)
- options: Optional configuration object containing:
- params: Query parameters (optional)
- contentType: Content-Type header (optional, defaults to application/json)
- dataProcessor: Function to process response data (optional)
del(url, data?, options?)
Performs HTTP DELETE request to delete a resource.
- url: The endpoint URL
- data: Request body data (optional)
- options: Optional configuration object containing:
- params: Query parameters (optional)
- contentType: Content-Type header (optional, defaults to application/json)
- dataProcessor: Function to process response data (optional)
FileService Methods
upload(url, params, file, fileKey?, dataProcessor?)
Performs synchronous file upload operation.
- url: The upload endpoint URL
- params: Additional parameters for the upload request
- file: The File object to upload
- fileKey: Optional form field name for the file (defaults to 'file')
- dataProcessor: Function to process response data (optional)
asyncUpload(url, params, file, callback, fileKey?)
Performs asynchronous file upload with progress monitoring and cancellation support.
- url: The upload endpoint URL
- params: Additional parameters for the upload request
- file: The File object to upload
- callback: Upload callback object with progress, error, and completion handlers
- fileKey: Optional form field name for the file (defaults to 'file')
- Returns: Promise that resolves to UploadProgress object for cancellation
download(url, filename, params, method?, formData?)
Performs file download operation.
- url: The download endpoint URL
- filename: The name to save the downloaded file
- params: Download request parameters
- method: Optional HTTP method (defaults to GET)
- formData: Optional form data for POST downloads
Error Handling
The library includes a custom ApiError class for handling API errors:
import { ApiError } from '@ticatec/restful_service_api';
try {
const result = await api.get('/users');
} catch (error) {
if (error instanceof ApiError) {
console.log('Status:', error.status);
console.log('Code:', error.code);
console.log('Details:', error.details);
}
}Types and Interceptors
import {
PreInterceptor,
PostInterceptor,
ErrorHandler,
DataProcessor,
RestfulOptions
} from '@ticatec/restful_service_api';
// RestfulOptions for configuring requests
const options: RestfulOptions = {
params: { page: 1, limit: 10 },
contentType: 'application/json',
dataProcessor: (data: any) => data.results || data
};
// Pre-interceptor for adding authentication headers
const preInterceptor: PreInterceptor = (method: string, url: string) => {
return {
headers: {
'Authorization': 'Bearer ' + getToken(),
'X-Request-ID': generateRequestId()
},
timeout: 30000
};
};
// Post-interceptor for response processing
const postInterceptor: PostInterceptor = async (data: any) => {
// Process response data
return data;
};
// Error handler
const errorHandler: ErrorHandler = (error: Error) => {
console.error('API Error:', error);
return true; // Return true if error is handled
};
// Data processor
const dataProcessor: DataProcessor = (data: any) => {
// Transform response data
return data.results || data;
};Content Types
Pre-defined content type constants:
import {
CONTENT_TYPE_NAME,
TYPE_JSON,
TYPE_HTML,
TYPE_TEXT
} from '@ticatec/restful_service_api';
// Usage
await api.post('/upload', data, {}, { contentType: TYPE_JSON });Implementation Example
Here's a complete example implementation using the native fetch API:
RestService Implementation
import RestService, {
ApiError,
PreInterceptor,
PostInterceptor,
RestfulOptions,
TYPE_JSON,
DataProcessor
} from '@ticatec/restful_service_api';
class FetchRestService implements RestService {
private baseURL: string;
private preInterceptor?: PreInterceptor;
private postInterceptor?: PostInterceptor;
constructor(baseURL: string, preInterceptor?: PreInterceptor, postInterceptor?: PostInterceptor) {
this.baseURL = baseURL;
this.preInterceptor = preInterceptor;
this.postInterceptor = postInterceptor;
}
async get(url: string, params?: any, dataProcessor?: DataProcessor): Promise<any> {
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
return this.request('GET', url + queryString, null, undefined, dataProcessor);
}
async post(url: string, data?: any, options?: RestfulOptions): Promise<any> {
const contentType = options?.contentType || TYPE_JSON;
const params = options?.params;
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
return this.request('POST', url + queryString, data, contentType, options?.dataProcessor);
}
async put(url: string, data?: any, options?: RestfulOptions): Promise<any> {
const contentType = options?.contentType || TYPE_JSON;
const params = options?.params;
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
return this.request('PUT', url + queryString, data, contentType, options?.dataProcessor);
}
async patch(url: string, data?: any, options?: RestfulOptions): Promise<any> {
const contentType = options?.contentType || TYPE_JSON;
const params = options?.params;
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
return this.request('PATCH', url + queryString, data, contentType, options?.dataProcessor);
}
async del(url: string, data?: any, options?: RestfulOptions): Promise<any> {
const contentType = options?.contentType || TYPE_JSON;
const params = options?.params;
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
return this.request('DELETE', url + queryString, data, contentType, options?.dataProcessor);
}
private async request(method: string, url: string, body?: any, contentType?: string, dataProcessor?: DataProcessor): Promise<any> {
const fullUrl = this.baseURL + url;
// Apply pre-interceptor
const interceptorResult = this.preInterceptor?.(method, fullUrl) || { headers: {} };
const headers = {
...interceptorResult.headers,
...(contentType && { 'Content-Type': contentType })
};
try {
const response = await fetch(fullUrl, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
signal: interceptorResult.timeout ? AbortSignal.timeout(interceptorResult.timeout) : undefined
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new ApiError(response.status, errorData);
}
let responseData = await response.json();
// Apply post-interceptor
if (this.postInterceptor) {
responseData = await this.postInterceptor(responseData);
}
// Apply data processor
if (dataProcessor) {
responseData = dataProcessor(responseData);
}
return responseData;
} catch (error) {
if (error instanceof ApiError) {
throw error;
}
throw new ApiError(0, { code: 'NETWORK_ERROR', message: error.message });
}
}
}
// Usage
const api = new FetchRestService('https://api.example.com');
const users = await api.get('/users');
const newUser = await api.post('/users', { name: 'John Doe' });
const partialUpdate = await api.patch('/users/1', { status: 'active' });
const fullUpdate = await api.put('/users/1', { name: 'Jane Doe', age: 30 });FileService Implementation
import FileService, { UploadCallback, UploadProgress, DataProcessor } from '@ticatec/restful_service_api';
class FetchFileService implements FileService {
private baseURL: string;
constructor(baseURL: string) {
this.baseURL = baseURL;
}
async upload(url: string, params: any, file: File, fileKey?: string, dataProcessor?: DataProcessor): Promise<any> {
const formData = new FormData();
formData.append(fileKey || 'file', file);
// Add additional parameters
Object.keys(params).forEach(key => {
formData.append(key, params[key]);
});
const response = await fetch(this.baseURL + url, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
const data = await response.json();
return dataProcessor ? dataProcessor(data) : data;
}
async asyncUpload(url: string, params: any, file: File, callback: UploadCallback, fileKey?: string): Promise<UploadProgress> {
const formData = new FormData();
formData.append(fileKey || 'file', file);
Object.keys(params).forEach(key => {
formData.append(key, params[key]);
});
const xhr = new XMLHttpRequest();
return new Promise((resolve) => {
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable && callback.progressUpdate) {
callback.progressUpdate(e.loaded);
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
const data = JSON.parse(xhr.responseText);
callback.onCompleted(data);
} else if (callback.handleError) {
callback.handleError(new Error(`Upload failed: ${xhr.statusText}`));
}
});
xhr.addEventListener('error', () => {
if (callback.handleError) {
callback.handleError(new Error('Network error'));
}
});
xhr.open(callback.method || 'POST', this.baseURL + url);
xhr.send(formData);
resolve({
cancel: () => xhr.abort()
});
});
}
async download(url: string, filename: string, params: any, method?: string, formData?: any): Promise<any> {
let fullUrl = this.baseURL + url;
if (method === 'POST' && formData) {
const form = new FormData();
Object.keys(formData).forEach(key => {
form.append(key, formData[key]);
});
const response = await fetch(fullUrl, {
method: 'POST',
body: form
});
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = filename;
a.click();
window.URL.revokeObjectURL(downloadUrl);
} else {
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
fullUrl += queryString;
const response = await fetch(fullUrl);
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = filename;
a.click();
window.URL.revokeObjectURL(downloadUrl);
}
}
}
// Usage
const fileApi = new FetchFileService('https://api.example.com');
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = fileInput.files[0];
// Simple upload
const result = await fileApi.upload('/upload', { userId: 123 }, file);
// Async upload with progress
const progress = await fileApi.asyncUpload('/upload', { userId: 123 }, file, {
method: 'POST',
progressUpdate: (loaded) => console.log(`Uploaded: ${loaded} bytes`),
onCompleted: (data) => console.log('Done!', data),
handleError: (err) => console.error('Error!', err)
});
// Download file
await fileApi.download('/files/document.pdf', 'my-document.pdf', { userId: 123 });Utility Functions
The library includes utility functions for common operations:
import utils from '@ticatec/restful_service_api/utils';
// Convert object to query string
const queryString = utils.toQueryString({ name: 'John', age: 30 });
// Returns: "name=John&age=30"
// Combine URL with parameters
const fullUrl = utils.combineUrl('/api/users', { page: 1, limit: 10 });
// Returns: "/api/users?page=1&limit=10"
// Generate HTTP request options
const options = utils.generateRequestOptions('POST', { id: 1 }, { name: 'John' });
// Returns: { method: 'POST', headers: {}, params: { id: 1 }, data: { name: 'John' } }
// Clean parameters (removes null, undefined, empty strings, trims values)
const cleanedParams = utils.cleanParams({ name: ' John ', age: null, email: '' });
// Returns: { name: 'John' }
// Function utilities
utils.invokeFunction(callback, arg1, arg2); // Safely invoke function if it exists
const isFunc = utils.isFunction(someValue); // Check if value is a functionAvailable Utility Functions
toQueryString(obj): Converts an object to URL query stringcombineUrl(url, params): Combines URL with query parametersgenerateRequestOptions(method, params, data): Generates HTTP request optionscleanParams(params): Cleans parameters by removing null/empty values and trimming stringsisFunction(value): Checks if a value is a functioninvokeFunction(func, ...args): Safely invokes a function if it exists
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see the LICENSE file for details.
Author
Henry Feng
For more examples and advanced usage, please check the documentation.
