Core Concepts
Understanding how Fync works under the hood
Core Concepts
Architecture Overview
Fync follows this structure:
Provider Factory → Module → Resources → Methods → API Calls- Provider Factory: Functions like
GitHub(),Spotify() - Module: The configured API client
- Resources: Groups of related endpoints (
repos,users,playlists) - Methods: Individual API operations (
getRepo,getUser,getPlaylist)
Modules & Resources
Every provider is a factory function that creates a module:
import { GitHub, Spotify } from '@remcostoeten/fync';
// Create modules
const github = GitHub({ token: process.env.GITHUB_TOKEN! });
const spotify = Spotify({ token: process.env.SPOTIFY_TOKEN! });
// Modules contain resources
console.log(github.repos); // Repository resource
console.log(github.users); // User resource
console.log(github.search); // Search resource
console.log(spotify.tracks); // Track resource
console.log(spotify.playlists); // Playlist resource
console.log(spotify.player); // Player resourceResource Structure
Each resource has:
basePath: Base URL path (e.g.,/repos,/users/{username})methods: Object of API methods with their HTTP verbs and paths
Path Interpolation
Path Parameters
API endpoints have placeholders in their paths. Fync fills these automatically:
// API endpoint: GET /repos/{owner}/{repo}
// Call:
const repo = await github.repos.getRepo({
owner: 'facebook', // Fills {owner}
repo: 'react' // Fills {repo}
});
// Result: GET https://api.github.com/repos/facebook/reactQuery Parameters
Any extra properties in your options object become query parameters:
// API endpoint: GET /repos/{owner}/{repo}/commits
// Call:
const commits = await github.repos.getRepoCommits({
owner: 'facebook', // Path param
repo: 'react', // Path param
per_page: 50, // Query param ?per_page=50
page: 2, // Query param ?page=2
sha: 'main' // Query param ?sha=main
});
// Result: GET /repos/facebook/react/commits?per_page=50&page=2&sha=mainMethod Signatures
Two Patterns
1. Methods with data (POST/PUT/PATCH)
// Pattern: (data, options)
method(dataObject, options)
// Example:
const newRepo = await github.repos.createRepo(
{ name: 'my-new-repo', private: true }, // data
{} // options
);2. Methods without data (GET/DELETE)
// Pattern: (options)
method(options)
// Example:
const repo = await github.repos.getRepo({
owner: 'facebook', // Path parameter
repo: 'react' // Path parameter
});DELETE with Body
Some APIs like Spotify support DELETE with a request body:
// DELETE with body (supported!)
await spotify.playlists.removeTracksFromPlaylist(
{ tracks: [{ uri: 'spotify:track:123' }] }, // data (body)
{ playlist_id: 'abc' } // options (path params)
);Response Handling
Automatic Parsing
Fync handles different response types automatically:
// JSON responses (most common)
const user = await github.getUser('octocat');
console.log(user.name); // Automatically parsed object
// Text responses
const readme = await github.repos.getRepoReadme({ owner: 'facebook', repo: 'react' });
console.log(typeof readme); // string (text content)
// Empty responses (204 No Content)
const result = await github.activity.starRepo({}, { owner: 'facebook', repo: 'react' });
console.log(result); // undefinedError Handling
Fync throws on non-2xx HTTP status codes:
try {
const user = await github.getUser('nonexistent-user-12345');
} catch (error) {
console.error('API Error:', error.message);
// Error includes status code, response text, etc.
}HTTP Configuration
Headers & Content Types
Fync handles common content types:
// JSON (default)
await github.repos.createRepo({ name: 'my-repo' });
// Form data (manually set header)
await someApi.createResource(
{ name: 'test', file: 'content' },
{},
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
// FormData (manually create)
const formData = new FormData();
formData.append('file', fileContent);
await someApi.uploadFile(formData);Authentication
Most providers use bearer token authentication:
// Automatic header setting
const github = GitHub({ token: 'ghp_xxxxxxxxxxxxxxxxxxxx' });
const spotify = Spotify({ token: 'BQD...access_token...' });
const calendar = GoogleCalendar({ token: 'ya29.access_token...' });For custom auth or headers:
const customClient = buildApi({
baseUrl: 'https://api.example.com',
auth: { type: 'bearer', token: 'custom-token' },
headers: {
'User-Agent': 'MyApp/1.0',
'Accept': 'application/vnd.api.v1+json'
}
});Helper Methods vs Low-Level Access
Helper Methods (Recommended)
Convenient, opinionated methods for common operations:
// Helper methods - easy to use
const user = await github.getUser('octocat');
const repo = await github.getRepository('facebook', 'react');
const playlist = await spotify.getPlaylist('37i9dQZF1DXcBWIGoYBM5M');
const events = await calendar.getUpcomingEvents('primary', 10);Low-Level Resource Access
Full control over API calls:
// Direct resource access - full control
const user = await github.users.getUser({ username: 'octocat' });
const repo = await github.repos.getRepo({ owner: 'facebook', repo: 'react' });
const playlist = await spotify.playlists.getPlaylist({ playlist_id: '37i9dQZF1DXcBWIGoYBM5M' });
const events = await calendar.events.listEvents({ calendarId: 'primary', maxResults: 10 });Both approaches are valid and use the same underlying HTTP client.
Advanced Patterns
Request/Response Interception
For logging, debugging, or modifying requests:
// Note: This is a conceptual example
const client = createApiClient({
interceptors: {
request: (config) => {
console.log('Making request:', config.method, config.url);
return config;
},
response: (response) => {
console.log('Got response:', response.status);
return response;
}
}
});Pagination
Many APIs return paginated results. Handle them like this:
// Get first page
const firstPage = await github.users.getUserRepos({
username: 'octocat',
per_page: 30,
page: 1
});
// Get next page (if available)
if (firstPage.length === 30) {
const secondPage = await github.users.getUserRepos({
username: 'octocat',
per_page: 30,
page: 2
});
}Batching Requests
For APIs that support batching, structure your data appropriately:
// Example: Get multiple tracks at once
const tracks = await spotify.tracks.getTracks({
ids: '4iV5W9uYEdYUVa79Axb7Rh,5U2Wd7q3gXUbPDjXEAP0BM'
});Best Practices
1. Use Helper Methods When Available
They're simpler and handle common patterns for you.
2. Handle Errors Gracefully
Always wrap API calls in try/catch blocks.
3. Use Type Safety
TypeScript definitions are included - leverage them!
4. Cache When Appropriate
Some APIs have rate limits - cache responses when you can.
5. Use Environment Variables for Tokens
Never commit tokens to your repository:
const github = GitHub({
token: process.env.GITHUB_TOKEN!
});Debugging
Enable Request Logging
Most providers support debug modes or logging:
// Example: Debug mode (conceptual)
const github = GitHub({
token: process.env.GITHUB_TOKEN!,
debug: true // Enable request/response logging
});Common Issues
- 401 Unauthorized: Check your token is valid
- 403 Forbidden: Check token permissions/rate limits
- 404 Not Found: Verify path parameters are correct
- 422 Unprocessable Entity: Check request body format
Understanding these core concepts will help you use Fync effectively across all supported providers!