Now.js Framework Documentation
HTTP/API Guide - Now.js Framework
HTTP/API Guide - Now.js Framework
Comprehensive guide for working with HTTP/API in Now.js Framework
📋 Table of Contents
- Overview
- Available Tools
- Comparison Table
- Choosing the Right Tool
- Common Patterns
- Best Practices
- Related Links
Overview
Now.js Framework provides multiple tools for working with HTTP/API. Each tool is designed for different purposes, helping you choose the right tool for your needs.
Usage Levels
┌─────────────────────────────────────────────────────────┐
│ ApiComponent │ ← UI Layer (Component Level)
│ (Declarative HTML) │
├─────────────────────────────────────────────────────────┤
│ ApiService │ ← Business Logic Layer (Service Level)
│ (Caching, Retry, Deduplication) │
├─────────────────────────────────────────────────────────┤
│ HttpClient │ ← Low-Level HTTP Layer (Base Level)
│ (http, httpThrow, simpleFetch) │
├─────────────────────────────────────────────────────────┤
│ ResponseHandler │ ← Server Response Layer (Response Level)
│ (Server-driven Actions) │
└─────────────────────────────────────────────────────────┘Available Tools
1. HttpClient (Low-Level)
Low-level HTTP Client built on fetch API with 4 main exports:
HttpClient- Class for creating custom instanceshttp- Ready-to-use instance (throwOnError: false)httpThrow- Instance that throws errors automatically (throwOnError: true)simpleFetch- Lightweight utility function for simple use cases
Use when:
- Need low-level control (interceptors, CSRF, timeout)
- Working with generic REST APIs
- Need high flexibility
Don't use when:
- Need caching, retry, deduplication
- Working with UI components
- Need authentication management
📖 Read full documentation: HttpClient
2. ApiService (Service-Level)
High-level HTTP Service with advanced features, suitable for business logic
Key Features:
- ✅ In-memory caching with expiration
- ✅ Request deduplication (prevent duplicate API calls)
- ✅ Retry mechanism (automatic retry on errors)
- ✅ Authentication management (Bearer, Basic, OAuth, Hybrid)
- ✅ Request tracking and analytics
- ✅ CSRF protection
- ✅ Abort controller management
Use when:
- Need caching for infrequently changing data
- Need retry logic for network errors
- Working with complex authentication
- Need to prevent duplicate API calls at the same time
Don't use when:
- Need simplicity and lightweight
- Working with real-time data (shouldn't cache)
- Don't need retry logic
📖 Read full documentation: ApiService
3. ApiComponent (UI-Level)
UI Component wrapper for declarative API calls via HTML attributes
Key Features:
- ✅ Declarative API calls (defined via HTML attributes)
- ✅ Autoload support
- ✅ Built-in loading/error/empty states
- ✅ Template rendering
- ✅ Event handlers (onSuccess, onError, onEmpty)
- ✅ Pagination support
- ✅ Polling support
Use when:
- Need to call API from UI components
- Need to display loading/error states
- Need to render data with templates
- Need pagination or polling
Don't use when:
- No UI element
- Need low-level control
- Working with complex business logic
📖 Read full documentation: ApiComponent
4. ResponseHandler (Response-Level)
Server-driven response handler that processes actions from server (redirect, alert, script execution, etc.)
Key Features:
- ✅ 17 action types (alert, redirect, script, html, etc.)
- ✅ Server-driven UI updates
- ✅ Auto-integration with form submissions
- ✅ Security features (XSS protection, script validation)
Use when:
- Server sends actions with response
- Need server-driven UI updates
- Working with form submissions
Don't use when:
- Need to control UI updates yourself
- Working with generic REST APIs
- Server doesn't send action-based responses
📖 Read full documentation: ResponseHandler
Comparison Table
| Feature | HttpClient | ApiService | ApiComponent | ResponseHandler |
|---|---|---|---|---|
| Usage Level | Low-level | High-level | UI Layer | Response Layer |
| Complexity | ⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️ |
| Caching | ❌ | ✅ | ✅ | ❌ |
| Retry Logic | ❌ | ✅ | ✅ | ❌ |
| Deduplication | ❌ | ✅ | ❌ | ❌ |
| Authentication | ⚠️ (Manual) | ✅ | ⚠️ (Via ApiService) | ❌ |
| CSRF Protection | ✅ | ✅ | ⚠️ (Via underlying service) | ❌ |
| Interceptors | ✅ | ✅ | ❌ | ❌ |
| UI Integration | ❌ | ❌ | ✅ | ✅ |
| Template Rendering | ❌ | ❌ | ✅ | ⚠️ (Via HTML action) |
| Loading States | ❌ | ❌ | ✅ | ❌ |
| Pagination | ❌ | ❌ | ✅ | ❌ |
| Polling | ❌ | ❌ | ✅ | ❌ |
| Server Actions | ❌ | ❌ | ❌ | ✅ |
| File Size | Small | Large | Medium | Small |
| Performance | Fast | Medium | Medium | Fast |
| Learning Curve | ⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️ |
Choosing the Right Tool
Scenario 1: Simple API Call
Example: Fetch users from API
// ✅ Best: Use simpleFetch (HttpClient)
const users = await simpleFetch.json('/api/users');
console.log(users);
// ✅ Best: Use http (HttpClient)
const response = await http.get('/api/users');
if (response.ok) {
console.log(response.data);
}Reason: Simple, lightweight, fast, no need for advanced features
Scenario 2: API Call with Caching
Example: Fetch categories that don't change frequently
// ✅ Best: Use ApiService
await ApiService.init({
cache: {
enabled: true,
expiry: { get: 300000 } // Cache 5 minutes
}
});
const categories = await ApiService.get('/api/categories');
console.log(categories.data);Reason: ApiService has built-in caching with expiration
Scenario 3: API Call with Authentication
Example: Call protected API endpoint
// ✅ Best: Use ApiService
await ApiService.init({
security: {
bearerAuth: true,
bearerTokenKey: 'auth_token', // Token in localStorage
authStrategy: 'hybrid' // HttpOnly cookie + access token
}
});
const profile = await ApiService.get('/api/user/profile');
console.log(profile.data);Reason: ApiService handles authentication automatically
Scenario 4: Display API Data in UI
Example: Display products list with loading state
HTML:
<div data-api-component
data-api-url="/api/products"
data-api-method="GET"
data-api-autoload="true"
data-api-cache="true"
data-api-cache-time="60000">
<template>
<div class="product">
<h3>{{name}}</h3>
<p>{{description}}</p>
<span class="price">{{price}}</span>
</div>
</template>
</div>JavaScript:
// ✅ Best: Use ApiComponent
const component = ApiComponent.create('[data-api-component]');Reason: ApiComponent handles UI states, templates, and data binding automatically
Scenario 5: Form Submission with Server Actions
Example: Submit form and let server decide what to do next
HTML:
<form id="loginForm" action="/api/login" method="post">
<input type="email" name="email" required>
<input type="password" name="password" required>
<button type="submit">Login</button>
</form>Server Response:
{
"status": "success",
"message": "Login successful",
"actions": [
{
"action": "alert",
"params": {
"message": "Welcome back!",
"type": "success"
}
},
{
"action": "redirect",
"params": {
"url": "/dashboard",
"delay": 1000
}
}
]
}JavaScript:
// ✅ Best: Use ResponseHandler
// ResponseHandler handles form submissions automatically
// and processes actions from serverReason: ResponseHandler is designed for server-driven UI updates
Scenario 6: API Call with Retry Logic
Example: Send analytics data that needs retry on network error
// ✅ Best: Use ApiService
await ApiService.init({
retryCount: 3,
retryDelay: 1000,
connection: {
retryOnNetworkError: true,
retryStatusCodes: [408, 429, 500, 502, 503, 504]
}
});
await ApiService.post('/api/analytics', {
event: 'page_view',
page: '/products'
});Reason: ApiService has built-in retry mechanism with exponential backoff
Scenario 7: Prevent Duplicate API Calls
Example: Prevent multiple button clicks that cause multiple API calls
// ✅ Best: Use ApiService
await ApiService.init({
deduplicate: true
});
// If user clicks multiple times at the same time
// ApiService waits for first request to finish and shares the result
button.addEventListener('click', async () => {
const result = await ApiService.get('/api/data');
console.log(result);
});Reason: ApiService has built-in request deduplication
Scenario 8: Working with Interceptors
Example: Add custom headers or logging
// ✅ Best: Use HttpClient
http.addRequestInterceptor(config => {
// Add custom header
config.headers['X-App-Version'] = '1.0.0';
// Logging
console.log('Request:', config.method, config.url);
return config;
});
http.addResponseInterceptor(
response => {
console.log('Response:', response.status);
return response;
},
error => {
console.error('Error:', error);
throw error;
}
);
const response = await http.get('/api/data');Reason: HttpClient has flexible interceptor API
Common Patterns
Pattern 1: Progressive Enhancement
Start with the simplest tool, then add complexity when needed
// Level 1: Simple API call
const users = await simpleFetch.json('/api/users');
// Level 2: Need caching and authentication
await ApiService.init({
cache: { enabled: true },
security: { bearerAuth: true }
});
const users = await ApiService.get('/api/users');
// Level 3: Need UI integration
const component = ApiComponent.create('#userList', {
url: '/api/users',
autoload: true,
cache: true
});Pattern 2: Layer Separation
Separate concerns by responsibility level
// ❌ Bad: Everything mixed together
async function loadUserProfile() {
const token = localStorage.getItem('token');
const response = await fetch('/api/user/profile', {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
document.getElementById('profile').innerHTML = `
<h1>${data.name}</h1>
<p>${data.email}</p>
`;
}
// ✅ Good: Separate concerns
// 1. API Layer - ApiService handles HTTP and auth
await ApiService.init({
security: { bearerAuth: true }
});
// 2. Data Layer - Service function
async function getUserProfile() {
return await ApiService.get('/api/user/profile');
}
// 3. UI Layer - ApiComponent handles rendering
const profileComponent = ApiComponent.create('#profile', {
url: '/api/user/profile',
template: 'profileTemplate'
});Pattern 3: Error Handling Strategy
Choose appropriate error handling strategy
// Strategy 1: Safe mode (doesn't throw error)
const response = await http.get('/api/data');
if (!response.ok) {
console.error('Error:', response.statusText);
return;
}
// Strategy 2: Throw mode (throws error)
try {
const response = await httpThrow.get('/api/data');
console.log(response.data);
} catch (error) {
console.error('Error:', error.message);
}
// Strategy 3: Service-level retry
await ApiService.init({
retryCount: 3,
connection: { retryOnNetworkError: true }
});
const response = await ApiService.get('/api/data');Pattern 4: Caching Strategy
Choose appropriate caching strategy for data
// Strategy 1: Long-lived data (categories, config)
await ApiService.init({
cache: {
enabled: true,
expiry: { get: 3600000 } // 1 hour
}
});
// Strategy 2: Short-lived data (search results)
await ApiService.init({
cache: {
enabled: true,
expiry: { get: 60000 } // 1 minute
}
});
// Strategy 3: No cache (real-time data)
await ApiService.init({
cache: { enabled: false }
});
// Strategy 4: Conditional cache
await ApiService.init({
cache: {
enabled: true,
responsePredicate: (response) => {
// Cache only success responses
return response.ok && response.status === 200;
}
}
});Best Practices
1. ✅ Choose the right tool for the job
// ❌ Bad: Use ApiService for simple API call
await ApiService.init({ /* many configs */ });
const users = await ApiService.get('/api/users');
// ✅ Good: Use simpleFetch when you don't need advanced features
const users = await simpleFetch.json('/api/users');2. ✅ Initialize services only once
// ❌ Bad: Initialize every time you call
async function fetchData() {
await ApiService.init({ /* config */ });
return await ApiService.get('/api/data');
}
// ✅ Good: Initialize once at app start
await ApiService.init({ /* config */ });
async function fetchData() {
return await ApiService.get('/api/data');
}3. ✅ Handle errors appropriately
// ❌ Bad: Don't handle error
const response = await http.get('/api/data');
console.log(response.data); // may be null
// ✅ Good: Check response.ok before using data
const response = await http.get('/api/data');
if (response.ok) {
console.log(response.data);
} else {
console.error('Error:', response.statusText);
}
// ✅ Better: Use httpThrow and try-catch
try {
const response = await httpThrow.get('/api/data');
console.log(response.data);
} catch (error) {
console.error('Error:', error.message);
}4. ✅ Use caching wisely
// ❌ Bad: Cache frequently changing data
await ApiService.init({
cache: {
enabled: true,
expiry: { post: 3600000 } // Cache POST responses?!
}
});
// ✅ Good: Cache only infrequently changing data
await ApiService.init({
cache: {
enabled: true,
expiry: {
get: 300000, // Cache GET 5 minutes
post: 0, // Don't cache POST
put: 0, // Don't cache PUT
delete: 0 // Don't cache DELETE
}
}
});5. ✅ Specify correct content type
// ❌ Bad: Send FormData but specify Content-Type
await http.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data' // Will lose boundary
}
});
// ✅ Good: Let browser handle Content-Type
await http.post('/api/upload', formData);
// browser sets: Content-Type: multipart/form-data; boundary=...6. ✅ Use AbortController for long-running requests
// ❌ Bad: No way to cancel request
button.addEventListener('click', async () => {
const response = await http.get('/api/large-data');
});
// ✅ Good: Can cancel request
const controller = new AbortController();
button.addEventListener('click', async () => {
const response = await http.get('/api/large-data', {
signal: controller.signal
});
});
cancelButton.addEventListener('click', () => {
controller.abort();
});7. ✅ Use interceptors for cross-cutting concerns
// ❌ Bad: Add token every time you call
const token = localStorage.getItem('token');
await http.get('/api/data', {
headers: { 'Authorization': `Bearer ${token}` }
});
// ✅ Good: Use interceptor
http.addRequestInterceptor(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
});
await http.get('/api/data');8. ✅ Use ResponseHandler for server-driven flows
// ❌ Bad: Client decides flow itself
const response = await http.post('/api/delete-item', { id: 123 });
if (response.ok) {
alert('Deleted successfully');
window.location.reload();
}
// ✅ Good: Let server decide flow
// Server response:
{
"status": "success",
"actions": [
{ "action": "alert", "params": { "message": "Deleted successfully" } },
{ "action": "reload" }
]
}
// ResponseHandler handles actions automatically9. ✅ Set appropriate timeout
// ❌ Bad: Same timeout for all requests
http.setTimeout(5000); // 5 seconds for all requests
// ✅ Good: Timeout by request type
http.setTimeout(30000); // Default 30 seconds
// For long-running requests
await http.post('/api/process-data', data, {
timeout: 120000 // 2 minutes
});
// For quick requests
await http.get('/api/ping', {
timeout: 3000 // 3 seconds
});10. ✅ Use typing and validation
// ❌ Bad: Don't validate response
const response = await http.get('/api/user');
console.log(response.data.name); // may be undefined
// ✅ Good: Validate response before use
const response = await http.get('/api/user');
if (response.ok && response.data) {
const user = response.data;
if (user.name && user.email) {
console.log(user.name);
} else {
console.error('Invalid user data');
}
}Related Links
Full Documentation
- 📖 HttpClient - Low-level HTTP Client
- 📖 ApiService - High-level HTTP Service
- 📖 ApiComponent - UI Component Wrapper
- 📖 ResponseHandler - Server-driven Actions
Related Documentation
- 📖 RouterManager - Client-side Routing
- 📖 FormManager - Form Handling
- 📖 StateManager - State Management
Usage Examples
- 🔗 Examples: Basic HTTP Requests
- 🔗 Examples: Authentication Flows
- 🔗 Examples: Caching Strategies
- 🔗 Examples: Error Handling
Summary
| If you need... | Use... |
|---|---|
| Simple API call | simpleFetch or http |
| Caching, Retry, Authentication | ApiService |
| Display API data in UI | ApiComponent |
| Server-driven UI updates | ResponseHandler |
| Interceptors, high flexibility | HttpClient (class) |
Golden Rule: Start with the simplest tool that works, then add complexity when needed