- Overview
- Quick Reference
- Basic Usage
- Installation
- Dependency Packages
- Initialization and Configuration
- Utilization
- Collection Records
- Root Scope Functions
- Utility Functions
- Credits
- License
- Contributing
ARM (API Resource Manager) is a JavaScript library designed to centralize data management and simplify interactions with APIs. By providing a structured approach to handling and storing fetched data, ARM promotes efficient and flexible data usage throughout your application.
- Centralized Data Storage: Organizes fetched data into easily accessible collections, acting as a single source of truth for your application's data.
- API Interactions: Manages API requests and responses, providing methods for common HTTP operations (GET, POST, PUT, DELETE).
- Caching: Optimizes performance by caching frequently accessed data, reducing API calls and improving response times.
- Request Management: Tracks ongoing requests to prevent redundancy and manages their state.
- Utility Functions: Offers helper functions for data manipulation, filtering, sorting, and other common operations.
- Root Scope: An object that can be used to store and manage global state.
- Collections: Stores fetched data in collections for efficient retrieval and management.
- Record Management: Provides methods to create, update, delete, and retrieve individual records within collections.
- Reactive Data: Employs observable patterns (through MobX) to enable real-time updates and dependency tracking.
- Asynchronous Operations: Handles API interactions asynchronously using Promises for non-blocking operations.
- Error Handling: Manages errors gracefully and provides informative feedback.
- Configurability: Allows customization of API endpoints, headers, and request behavior.
- Extensibility: Can be integrated with other libraries and frameworks to fit various application architectures.
- Centralized Data Access: Provides a single source of truth for application data, ensuring consistency and reducing data duplication.
- Improved Performance: Caching and optimized request management enhance application speed.
- Enhanced Developer Experience: Simplifies data management and reduces boilerplate code.
- Flexibility: Can be used across different components and parts of an application.
- Maintainability: Promotes code organization and reduces potential inconsistencies.
By centralizing data management and offering flexible access to it, ARM empowers developers to build more efficient, scalable, and maintainable applications.
| Method | Description |
|---|---|
query(resource, params, config) |
Fetch multiple records from server |
queryRecord(resource, params, config) |
Fetch a single record from server |
findAll(resource, config) |
Fetch all records from server |
findRecord(resource, id, params, config) |
Fetch a single record by ID from server |
peekAll(collectionName) |
Read all records from local collection |
peekRecord(collectionName, id) |
Read a single record from local collection |
getCollection(collectionName) |
Get all records in a collection |
getAlias(aliasName, fallbackRecords) |
Get records stored under an alias |
getRequestAlias(aliasName) |
Get the request hash object for an alias |
createRecord(collectionName, record, randomId) |
Create a new local record |
pushPayload(collectionName, records) |
Push raw records into a collection |
unloadRecord(record) |
Remove a record from local collection |
clearCollection(collectionName) |
Clear all records from a collection |
setHost(host) |
Set the API base host URL |
setNamespace(namespace) |
Set the API namespace |
setHeadersCommon(key, value) |
Set a common request header |
setPayloadIncludeReference(key) |
Set the included payload reference key |
setGlobal() |
Attach ARM instance to window.ARM |
setRootScope(property, value) |
Set a global state value |
getRootScope(property) |
Get a global state value |
ajax(config) |
Make a raw Axios request with ARM config |
// Example usage in ReactJS/NextJS
import { observer } from 'mobx-react'
import { ARM } from '@/components/providers/arm-config-provider'
const App = observer(() => {
// GET /addresses/2519858?include=user
const {
isLoading,
isError,
data: address,
} = ARM.findRecord(
'addresses',
2519858,
{ include: 'user' },
{ alias: 'customerAddress' },
)
return (
<div className="App">
{isLoading && <span>Loading...</span>}
{!isLoading && (
<div className="form">
<label>Address1 </label>
<input
value={address.get('attributes.address1')}
onChange={(event) =>
address.set('attributes.address1', event.target.value)
}
/>
<button
onClick={() => {
address
.save()
.then((result) => console.log(result))
.catch((error) => console.log(error))
}}>
{address.get('isLoading') ? 'Saving' : 'Save'}
</button>
</div>
)}
</div>
)
})
export default Appnpm install arm-js-library --save
npm install mobx-react --save
-
Create an
arm-config-providercomponent to hold the ARM instance.
See example here'use client' // Omit this line if you are not using NextJS import ApiResourceManager from 'arm-js-library' const COLLECTIONS = ['addresses', 'users'] export const ARM = new ApiResourceManager(COLLECTIONS) const ARMConfigProvider = ({ children }) => { return <>{children}</> } export default ARMConfigProvider
-
Use the
arm-config-providercomponent in your application.
For NextJS projects, wrap it in a centralized client component (application-providers) to prevent SSR-related errors.
See example here'use client' import dynamic from 'next/dynamic' const ARMConfigProvider = dynamic( () => import('@/components/providers/arm-config-provider'), { ssr: false }, ) const ApplicationProviders = ({ children }) => { return ( <> <ARMConfigProvider>{children}</ARMConfigProvider> </> ) } export default ApplicationProviders
Wrap the root layout
src/app/layout.jsxwith theapplication-providerscomponent.
See example hereimport ApplicationProviders from '@/components/providers/application-providers' export default function RootLayout({ children }) { return ( <html lang="en"> <body className="antialiased"> {/* Wrap your application with application-providers component */} <ApplicationProviders>{children}</ApplicationProviders> </body> </html> ) }
For non-NextJS projects, wrap the root app
src/index.jsdirectly witharm-config-provider.import ARMConfigProvider from '@/components/providers/arm-config-provider' import ReactDOM from 'react-dom/client' import App from './App' const root = ReactDOM.createRoot(document.getElementById('root')) root.render( <ARMConfigProvider> <App /> </ARMConfigProvider> )
Configure the ARM instance from wherever it is stored before using it in your application.
Required configurations
-
setHost(value)
- Set the API endpoint host URL.
- By default, host is set to
window.location.origin.
ARM.setHost('https://www.test-demo.com')
-
setHeadersCommon(key, value)
- Set common request headers required for API calls.
ARM.setHeadersCommon('Authorization', `${token}`) ARM.setHeadersCommon('Content-Type', 'application/vnd.api+json') ARM.setHeadersCommon('X-Client-Platform', 'Web')
-
setNamespace(value)
- Set the API endpoint namespace.
- By default, namespace is set to
'api/v1'.
ARM.setNamespace('api/v1')
Optional configurations
-
setGlobal()
- Attaches the ARM instance to the global browser
windowobject aswindow.ARM.
ARM.setGlobal()
- Attaches the ARM instance to the global browser
-
setPayloadIncludeReference(value)
- Set the reference key used to map
includedpayload data to their collections.
// Example payload: // { // data: [...], // included: [ { id: 1, type: 'addresses' } ] // meta: {...} // } ARM.setPayloadIncludeReference('type')
- Set the reference key used to map
Import the stored ARM instance from arm-config-provider to use ARM features.
import { ARM } from '@/components/providers/arm-config-provider'-
query(resource, params, config)
- Fetch multiple records from the server.
- See example here
params- required |config- optional
ARM.query( 'addresses', { sort: '-id', include: 'user', }, { alias: 'customerAddresses', }, )
-
queryRecord(resource, params, config)
- Fetch a single record from the server.
- See example here
params- optional |config- optional
ARM.queryRecord( 'addresses', { id: 2519858, sort: '-id', include: 'user', }, { alias: 'customerAddress' }, )
-
findAll(resource, config)
- Fetch all records from the server.
- See example here
config- optional
ARM.findAll('addresses', { alias: 'customerAddresses', })
-
findRecord(resource, id, params, config)
- Fetch a single record by ID from the server.
- See example here
id- required |params- optional |config- optional
ARM.findRecord( 'addresses', 2519858, { include: 'user' }, { alias: 'customerAddress', }, )
See example here
// Example: https://www.test-demo.com/api/v1/addresses/1?include=user
ARM.findRecord(
'addresses',
2519858,
{
include: 'user',
},
{
skip: true,
alias: 'customerAddress',
},
)-
resource - String
https://www.test-demo.com/api/v1/addresses/2519858?include=user- Endpoint resource name. Also serves as the collection name defined during ARM initialization.
-
id - Number | String
https://www.test-demo.com/api/v1/addresses/2519858?include=user- Endpoint ID parameter. Accepts both numeric and string IDs.
-
params - Object
https://www.test-demo.com/api/v1/addresses/2519858?include=user- Endpoint query string parameters.
-
config - Object
- Contains request configuration options:
skip,alias,autoResolve,ignorePayload,override.
{ // Serve as a request go-signal. Useful when Request B depends on Request A. skip: true, // A human-readable identifier for the records returned from the server. // Can be accessed anywhere via ARM.getAlias('customerAddress'). alias: 'customerAddress', // Controls whether the request function returns: // false - a Promise (manual resolve, gives access to raw response) // true - Observable/Reactive data (auto-resolves, default behavior) // Note: only available on query, queryRecord, findAll, findRecord. autoResolve: false, // List of payload keys to omit from the request body. ignorePayload: ['attributes.address2', 'attributes.address1'], // Override the default Axios request configuration. // Supports host, namespace, path, and headers. // Before: https://www.test-demo.com/api/v1/users/1 // After: https://www.another-test-demo.com/api/v2/update-users/1 override: { host: 'https://www.another-test-demo.com', namespace: 'api/v2', path: `update-users/${user.get('id')}`, headers: { 'X-Client-Platform': 'Symbian', } } }
- Contains request configuration options:
See example here
// All properties are observable and update automatically when the request completes.
{
isLoading: true,
isError: false,
isNew: true,
data: [],
error: null,
included: [],
meta: {},
reload: function
}-
isLoading - Boolean
- Current loading state of the request.
- Starts as
truewhen the request is initiated, resets tofalsewhen complete.
-
isError - Boolean
- Current error state of the request.
- Set to
trueif the request encounters an error,falseotherwise.
-
isNew - Boolean
- Indicates whether this is the first time the request has been made.
- Starts as
true. Set tofalseon subsequent calls since ARM caches and deduplicates requests by default. Useskipin the config to force a re-fetch.
-
data - Array | Object
- Contains the response payload. Defaults to an empty array or object depending on the request function used.
-
error - Object | String
- Contains the error returned by the request. Defaults to
null.
- Contains the error returned by the request. Defaults to
-
included - Array
- Contains the
includedproperty from the response payload (JSON API).
- Contains the
-
meta - Object
- Contains the
metaproperty from the response payload (JSON API).
- Contains the
-
reload - Function
- Re-executes the original request and automatically updates the request hash and relevant collections.
-
peekAll(collectionName)
- Read all records from a local collection without triggering an API request.
- See example here
ARM.peekAll('addresses')
-
peekRecord(collectionName, collectionRecordId)
- Read a single record from a local collection without triggering an API request.
- See example here
ARM.peekRecord('addresses', 2519858)
-
getCollection(collectionName)
- Retrieve all records currently stored in a collection.
- See example here
ARM.getCollection('addresses')
-
getAlias(aliasName, fallbackRecords)
- Retrieve records stored under an alias from a previous request.
- See example here
fallbackRecords- optional
const addresses = ARM.getAlias('customerAddresses', []) ARM.findAll('addresses', { alias: 'customerAddresses' }) <ul> {addresses.map((address, index) => ( <li key={index}>{address.get('id')}</li> ))} </ul>
-
getRequestAlias(aliasName)
- Retrieve the full returned object (including
isLoading,data,meta, etc.) for a previously aliased request. - See example here
const addresses = ARM.getRequestAlias('customerAddresses') ARM.findAll('addresses', { alias: 'customerAddresses' }) <ul> {addresses.data.map((address, index) => ( <li key={index}>{address.get('id')}</li> ))} </ul>
- Retrieve the full returned object (including
- createRecord(collectionName, collectionRecord, collectionRecordRandomId)
- Create a new record in a local collection.
- See example here
collectionRecorddefaults to{}- optional |collectionRecordRandomIddefaults totrue- optional
// Usage #1 - set attributes after creation const newAddress = ARM.createRecord('addresses') newAddress.set('attributes.kind', 'school') newAddress.set('attributes.label', 'My school') // Usage #2 - pass initial attributes const newAddress = ARM.createRecord('addresses', { attributes: { kind: 'school', label: 'My school' }, }) // Persist the record to the server via POST /addresses newAddress.save()
-
unloadRecord(collectionRecord)
- Remove a record from the local collection without deleting it from the server.
- See example here
const address = ARM.peekRecord('addresses', 2519858) // Removes the record locally only - does not call the server. ARM.unloadRecord(address)
-
clearCollection(collectionName)
- Remove all records from a collection and unload them from any related aliases and request hashes.
- See example here
ARM.clearCollection('addresses')
- pushPayload(collectionName, collectionRecords)
- Push raw records directly into a collection. Useful when fetching data outside of ARM's standard request functions.
- See example here
ARM.ajax({ method: 'get', url: 'addresses/12345', }).then((results) => { // Adds or updates records in the collection. ARM.pushPayload('addresses', results.data.data) })
Collection records are the individual objects stored inside ARM collections. Each record is automatically decorated with state properties, getter/setter functions, and request functions when it enters a collection.
// Example record shape returned from the API
{
"id": 2519858,
"type": "addresses",
"attributes": {
"address1": "Test Address 1",
"address2": "171872.9.2222",
"kind": "office",
"label": "Anabu Hills",
"latitude": "14.394261",
"longitude": "120.940783"
}
}See example here
-
isLoading - Boolean
- Current loading state of the record. Defaults to
false. - Set to
truewhensave,reload, ordestroyRecordis initiated; reset tofalsewhen complete.
address.get('isLoading')
- Current loading state of the record. Defaults to
-
isError - Boolean
- Current error state of the record. Defaults to
false. - Set to
trueifsave,reload, ordestroyRecordreceives an error.
address.get('isError')
- Current error state of the record. Defaults to
-
isPristine - Boolean
- Indicates whether the record has unsaved local changes. Defaults to
true. - Set to
falsewhen the record is modified; restored totrueafter a successful save or rollback.
address.get('isPristine')
- Indicates whether the record has unsaved local changes. Defaults to
-
isDirty - Boolean
- The inverse of
isPristine. Defaults tofalse. - Set to
truewhen the record has unsaved local changes.
address.get('isDirty')
- The inverse of
See example here
-
get(key)
- Read a single property from the record using dot-notation.
address.get('id') // Returns 2519858 address.get('attributes.label') // Returns 'Anabu Hills'
-
set(key, value)
- Set a single property on the record. Updates
isDirtyandisPristineflags automatically.
address.get('attributes.kind') // Returns 'office' address.set('attributes.kind', 'school') address.get('attributes.kind') // Returns 'school'
- Set a single property on the record. Updates
-
setProperties(values)
- Set multiple properties on the record at once.
address.setProperties({ attributes: { kind: 'school', label: 'My School' }, }) address.get('attributes.kind') // Returns 'school' address.get('attributes.label') // Returns 'My School'
-
save(collectionConfig)
- Persist record changes to the server.
- See example here
- Calls
POST /addressesfor new records,PUT /addresses/2519858for existing ones. collectionConfig- optional | supportsskip,alias,autoResolve,ignorePayload,override
address.save() // With config address.save({ ignorePayload: ['attributes.address2'] })
-
reload()
- Re-fetch the record from the server and update the local copy.
- See example here
- Calls
GET /addresses/2519858. collectionConfig- optional | supportsskip,alias,autoResolve,ignorePayload,override
address.reload() // With config address.reload({ override: { namespace: 'api/v2', }, })
-
rollbackAttributes()
- Revert the record's attributes to their last saved state without making an API request.
- See example here
address.rollbackAttributes()
-
destroyRecord(collectionConfig)
- Permanently delete the record from the server and remove it from the local collection.
- See example here
- Calls
DELETE /addresses/2519858. collectionConfig- optional | supportsskip,alias,autoResolve,ignorePayload,override
address.destroyRecord() // With config address.destroyRecord({ override: { host: 'https://ww7.test-demo.com', namespace: 'api/v2', path: `destroy-addresses/${address.get('id')}`, }, })
-
getCollection(collectionName, collectionConfig)
- Retrieve related records from another collection based on relationship data on the current record.
- See example here
- When
async: true, fetches missing records from the server automatically. - When
async: false, returns only records already present in the local collection.
Option Type Description referenceKeyString Dot-notation path to the relationship data on the record asyncBoolean Whether to fetch missing related records from the server filterByObject Filter the returned records by property values sortByArray Sort criteria in 'property:direction'formatoverrideObject Override the default request config (host, namespace, path, headers) const { isLoading, data: user } = ARM.findRecord( 'users', 12980860, {}, { alias: 'currentUser' }, ) { !isLoading && ( <ul> {user .getCollection('addresses', { referenceKey: 'relationships.addresses.data', async: true, sortBy: ['id:desc'], filterBy: { attributes: { label: 'Test', }, }, override: { namespace: 'api/v2', }, }) .map((address, index) => ( <li key={index}>{address.get('id')}</li> ))} </ul> ) }
The root scope is a globally accessible object for storing and sharing state across your application.
-
setRootScope(rootScopeProperty, rootScopeValue)
- Set a value on the root scope.
ARM.setRootScope('fullName', 'John Doe')
-
getRootScope(rootScopeProperty)
- Retrieve a value from the root scope.
ARM.getRootScope('fullName') // Returns 'John Doe'
A collection of utility functions built on Lodash for common data manipulation and validation tasks.
// Sample data used in examples below
const addresses = [
{
id: 1,
attributes: { kind: 'office', label: 'My Office' },
},
{
id: 2,
attributes: { kind: 'school', label: 'My School' },
},
{
id: 3,
attributes: { kind: 'school', label: "My Brother's School" },
},
]-
findBy(objects, findProperties)
- Returns the first element matching the given properties.
ARM.findBy(addresses, { id: 1 }) // Returns the record with id 1
-
findIndexBy(objects, findIndexProperties)
- Returns the index of the first element matching the given properties.
ARM.findIndexBy(addresses, { attributes: { kind: 'office' } }) // Returns 0
-
filterBy(objects, filterProperties)
- Returns all elements matching the given properties.
ARM.filterBy(addresses, { attributes: { kind: 'school' } }) // Returns records with ids 2 and 3
-
uniqBy(objects, uniqByProperty)
- Returns a new array with duplicates removed based on a specific property.
ARM.uniqBy(addresses, 'attributes.kind') // Returns records with ids 1 and 2
-
uniq(values)
- Returns a new array with duplicate primitive values removed.
ARM.uniq([1, 2, 2, 3, 3, 3]) // Returns [1, 2, 3]
-
groupBy(objects, groupByProperty)
- Groups objects into arrays keyed by the specified property.
ARM.groupBy(addresses, 'attributes.kind') // Returns { office: [{ id: 1 }], school: [{ id: 2 }, { id: 3 }] }
-
mapBy(objects, mapByProperty)
- Returns a new array containing only the specified property from each object.
ARM.mapBy(addresses, 'attributes.kind') // Returns ['office', 'school', 'school']
-
firstObject(objects)
- Returns the first element of the array, or
undefinedif empty.
ARM.firstObject(addresses) // Returns the record with id 1
- Returns the first element of the array, or
-
lastObject(objects)
- Returns the last element of the array, or
undefinedif empty.
ARM.lastObject(addresses) // Returns the record with id 3
- Returns the last element of the array, or
-
mergeObjects(objects, otherObjects)
- Combines two arrays into one, removing duplicate entries.
ARM.mergeObjects(addresses, otherAddresses)
-
chunkObjects(objects, chunkSize)
- Splits an array into smaller arrays of the specified size.
ARM.chunkObjects(addresses, 2)
-
sortBy(objects, sortProperties)
- Sorts an array by the specified properties and directions.
ARM.sortBy(addresses, ['id:asc']) // Returns records ordered 1, 2, 3 ARM.sortBy(addresses, ['id:desc']) // Returns records ordered 3, 2, 1
-
sum(objects)
- Returns the sum of an array of numbers.
ARM.sum([100, 200, 300]) // Returns 600
-
sumBy(objects, sumByProperty)
- Returns the sum of a specific numeric property across an array of objects.
ARM.sumBy( [ { id: 1, name: 'Banana', price: 100 }, { id: 2, name: 'Apple', price: 200 }, { id: 3, name: 'Orange', price: 300 }, ], 'price', ) // Returns 600
-
ajax(config)
- Make a raw Axios request using the ARM base configuration. Accepts all standard
axios.requestconfig options.
ARM.ajax({ method: 'get', baseURL: 'https://other-api.test-demo.com', url: '/api/v1/addresses', }) .then((results) => console.log(results)) .catch((errors) => console.log(errors))
- Make a raw Axios request using the ARM base configuration. Accepts all standard
-
isEmpty(value)
- Returns
trueif the value isnull,undefined, an empty string, empty array, or empty object.
ARM.isEmpty(value)
- Returns
-
isPresent(value)
- Returns the opposite of
isEmpty.
ARM.isPresent(value)
- Returns the opposite of
-
isEqual(value, other)
- Performs a deep comparison between two values.
ARM.isEqual(value, other)
-
isNumber(value)
- Returns
trueif the value is a number.
ARM.isNumber(value)
- Returns
-
isNil(value)
- Returns
trueif the value isnullorundefined.
ARM.isNil(value)
- Returns
-
isNull(value)
- Returns
trueif the value isnull.
ARM.isNull(value)
- Returns
-
isGte(value, other)
- Returns
trueifvalueis greater than or equal toother.
ARM.isGte(value, other)
- Returns
-
isGt(value, other)
- Returns
trueifvalueis greater thanother.
ARM.isGt(value, other)
- Returns
-
isLte(value, other)
- Returns
trueifvalueis less than or equal toother.
ARM.isLte(value, other)
- Returns
-
isLt(value, other)
- Returns
trueifvalueis less thanother.
ARM.isLt(value, other)
- Returns
- Axl Asuncion - Original logo/design creator
- Luigi Cruz - Features and bugs feedback
- Dickson Palomeras - Features and bugs feedback
This project is licensed under the MIT License.
Contributions are welcome! Please read the CONTRIBUTING.md guide before submitting any changes.