- Overview
- Basic Usage
- Installation
- Dependency Packages
- Utilization
- Collection Records:
Properties and Functions
- Utility Functions
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.
- 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 (likely through a library like 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.
// Example usage in ReactJS
import { observer } from 'mobx-react'
import { ARM } from '@components/arm-config-wrapper'
const App = observer(() => {
const { isLoading, isError, data: address } = ARM.findRecord(
'addresses',
123456,
{ 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 App
npm install arm-js-library --save
npm install mobx-react --save
Create arm-config-wrapper
component that will store the new ARM
instance.
- Store it on component wrapper
src/components/arm-config-wrapper/index.js
here's an example// Tag component wrapper as client for NextJS specific only 'use client' // Create a new instance of ARM import ApiResourceManager from 'arm-js-library' // Create an array of collections to initialize const collections = ['addresses', 'users'] // Export new instance of ARM for later utilization export const ARM = new ApiResourceManager(collections) // Main config wrapper const ARMConfigWrapper = ({ children }) => { return <>{children}</> } export default ARMConfigWrapper
- For
NextJS
project, wrap root layoutsrc/app/layout.js
witharm-config-wrapper
component here's an exampleimport dynamic from 'next/dynamic' const ARMConfigWrapper = dynamic( () => import('../components/arm-config-wrapper'), { ssr: false } ) export default function RootLayout({ children }) { return ( <html lang="en"> <body> <ARMConfigWrapper>{children}</ARMConfigWrapper> </body> </html> ) }
- For non
NextJS
project, wrap root appsrc/index.js
witharm-config-wrapper
component.import ARMConfigWrapper from '@components/arm-config-wrapper' import ReactDOM from 'react-dom/client' import App from './App' const root = ReactDOM.createRoot(document.getElementById('root')) root.render( <ARMConfigWrapper> <App /> </ARMConfigWrapper> )
Configure stored ARM instance from where you stored it, to be able to use it on your application.
Required configurations
- setHost(value)
// Set 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 on calling API endpoints // ie. Authoization, Content-Type, etc. ARM.setHeadersCommon('Authorization', `${token}`) ARM.setHeadersCommon('Content-Type', 'application/vnd.api+json') ARM.setHeadersCommon('X-Client-Platform', 'Web')
- setNamespace(value)
// Set namespace for API endpoint host URL // By default namespace is set to 'api/v1' ARM.setNamespace('api/v1')
Optional configurations
- setGlobal(value)
// Set ARM instace to global // This will make ARM instance available on browser window object via window.ARM // Example: // console.log(window.ARM) ARM.setGlobal()
- setPayloadIncludeReference(value)
// Set payload included reference key // Payload included reference key serve as mapper to determine what collection // the data received belongs to // Example: // { // data: [...], // included: [ { id: 1, type: 'addresses' } ] // } ARM.setPayloadIncludeReference('type')
To be able to use ARM features. You have to import the stored ARM instance from arm-config-wrapper
component.
// ARM instance is stored on src/components/arm-config-wrapper/index.js
import { ARM } from '@components/arm-config-wrapper'
- query(resource, params, config)
- Querying multiple records from the server.
- Support query params. - required
- Support config. - optional
ARM.query( 'addresses', { sort: '-id', include: 'user', }, { alias: 'customerAddresses', } )
- queryRecord(resource, params, config)
- Querying for a single record from the server.
- Support query params. - required
- Support config. - optional
ARM.queryRecord( 'addresses', { id: 123456, sort: '-id', include: 'user', }, { alias: 'customerAddress' } )
- findAll(resource, config)
- Retrieving multiple records from the server.
- Support config. - optional
ARM.findAll('addresses', { alias: 'customerAddresses', })
- findRecord(resource, id, params, config)
- Retrieving single record from the server.
- Params ID by default. - required
- Support query params. - required
- Support config. - optional
ARM.findRecord( 'addresses', 123456, { include: 'user' }, { alias: 'customerAddress', } )
// Example: https://www.test-demo.com/api/v1/addresses/1?include=user
ARM.findRecord('addresses', 123456,
{
include: 'user'
},
{
skip: true,
alias: 'customerAddress',
}
)
- resource - String
https://www.test-demo.com/api/v1/
addresses/1?include=user
- Endpoint resource name.
- Serve as collection name defined on the collection intialization of ARM instance.
- id - Number
https://www.test-demo.com/api/v1/addresses/
1?include=user
- Endpoint id parameter.
- params - Object
https://www.test-demo.com/api/v1/addresses/1?
include=user- Endpoint query string parameters.
- config - Object
- Contains request config such as
(skip, alias, autoResolve, ignorePayload, override)
which are currently available.
{ // Skip serve as request go signal to proceed // if Request B has dependency on Request A skip: true, // Alias serve as identifier for the records obtain from the server. // Can be used anywhere in your application through ARM.getAlias('customerAddress') alias: 'customerAddress' , // Auto resolve serve as flag if the request functions will return // 1. Promise Function // - To handle success and errors on manual resolve) if autoResolve is set to false // 2. Observable/Reactive Data // - To handle success and errors on auto resolve) if autoResolve is set to true // Note: autoResolve is only available on query, queryRecord, findAll, findRecord functions. // By default autoResolve is set to true. autoResolve: false, // Ignore payload serve as list of keys to be omitted on request payload. ignorePayload: ['attributes.address2', 'attributes.address1'], // Override serve as request override for the default configuration of axios current request. // Currently support host, namespace, path and headers for the meantime. // Example: // Before override: https://www.test-demo.com/api/v1/users/1 // After override: 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 config such as
- isLoading - Boolean
- Current loading state of the request.
- By default set to true.
- Set to true once the request is initiated and set to false once request is done.
- isError - Boolean
- Current error state of the request.
- By default set to false.
- Set to true if the request received/encountered an error and set to false if none.
- isNew - Boolean
- Identifier if the request is newly created.
- By default set to true.
- Set to true if the request is already initiated once and set to false once it is already intiated before. Request functions are built with optimization, it does not repeatedly executing API request. Since it is optimized, it can be override using skip from request configuration.
- data - Array || Object
- Contains the request returned payload.
- By default has value of an empty array or object depending on the request function used.
- error - Object || String
- Contains the request returned error.
- By default has value of a null.
- included - Array
- Contains the request returned payload property included.
- Specifically for JSON API.
- meta - Object
- Contains the request returned payload property meta.
- Specifically for JSON API.
// Returned object data properties are observable
// It will automatically update once the request is already done
{
isLoading: true,
isError: false,
isNew: true,
data: [],
error: null,
included: [],
meta: {},
}
- peekAll(collectionName)
- Retrieving multiple records from collection.
ARM.peekAll('addresses')
- peekRecord(collectionName, collectionRecordId)
- Retrieving single record from collection.
- Params ID by default. - required
ARM.peekRecord('addresses', 123456)
- getCollection(collectionName)
- Retrieving all records from collection.
ARM.getCollection('addresses')
- getAlias(collectionName, collectionFallbackRecord)
- Retrieving records from aliased request results.
- Support collectionFallbackRecord. - optional
const addresses = ARM.getAlias('customerAddresses', []) ARM.findAll('addresses', { alias: 'customerAddresses' }) <ul> {addresses.map((address, index) => ( <li key={index}>{address.get('id')}</li> ))} </ul>
- createRecord(collectionName, collectionRecord, collectionRecordRandomId)
- Create new collection record.
- By default collectionRecord params is set to empty object if omitted - required
- By default collectionRecordRandomId params is set to true - optional
// Usage #1 // Can ommit collectionRecord on createRecord initialization const newAddress = ARM.createRecord('addresses') newAddress.set('attributes.kind', 'school') newAddress.set('attributes.label', 'My school') // Usage #2 // Can ommit collectionRecord on createRecord initialization const newAddress = ARM.createRecord('addresses', { attributes: { kind: 'school', label: 'My school' } }) // Persist collection record to server. // Will call POST /addresses newAddress.save()
- unloadRecord(collectionRecord)
- Remove record from collection only.
// Collection record to be remove collection. const address = ARM.peekRecord('addresses', 123456) // This will remove the record from collection and will not // remove permanently from the server. ARM.unloadRecord(address)
- clearCollection(collectionName)
- Remove all records from collection only.
ARM.clearCollection('addresses')
- pushPayload(collectionName, collectionRecords)
- Push raw collection record/records to respective collections.
// Retrieve raw data with barebone ajax/fetch function. ARM.ajax({ method: 'get', url: 'addresses/12345' }).then(results => { // Will add/update collection records. ARM.pushPayload('addresses', results.data.data) })
// Example response data from API
// See available properties, getter and setter functions and request functions below.
{
"id": 123456,
"type": "addresses",
"attributes": {
"address1": "Test Address 1",
"address2": "1718729541222",
"kind": "office",
"label": "Anabu Hills",
"latitude": "14.394261",
"longitude": "120.940783"
}
}
- State Properties
- isLoading - Boolean
- Current loading state of the record.
- By default set to false.
- Set to true once request functions (save, reload, destroyRecord) are initiated and set to false once done.
address.get('isLoading')
- isError - Boolean
- Current error state of the record.
- By default set to false.
- Set to true once request functions (save, reload, destroyRecord) received an error and set to false if none.
address.get('isError')
- isPristine - Boolean
- Current pristine state of the record.
- By default set to true.
- Set to false if the record is modified and set to true once reverted.
address.get('isPristine')
- isDirty - Boolean
- Current dirty state of the record.
- By default set to false.
- Set to true if the record is modified and set to false once reverted.
address.get('isDirty')
- isLoading - Boolean
- Getter and Setter Functions
- get(key)
- Single property getter function.
- Passed arguments:
- key - String
// Returned value 123456 address.get('id') // Returned value 'office' address.get('attributes.label')
- set(key, value)
- Single property setter function.
- Passed arguments:
- key - String
- value - Primitive
// Returned value 'office' address.get('attributes.kind') // Set property label of attributes address.set('attributes.kind', 'school') // Returned value 'office' address.get('attributes.kind')
- setProperties(value)
- Multiple properties setter function.
- Passed arguments:
- value - Object
// Returned value 'office' address.get('attributes.kind') // Returned value 'Anabu Hills' address.get('attributes.label') // Set properties label and kind of attributes address.setProperties({ attributes: { kind: 'school', label: 'My School' } }) // Returned value 'school' address.get('attributes.kind') // Returned value 'My School' address.get('attributes.label')
- get(key)
- Request Functions
- save(collectionConfig)
- Persist collection record changes to server.
- Create a new record to server only if it doesn't already exist in the database.
- Will call POST method:
POST /addresses
- Will call POST method:
- Update existing record to server.
- Will call PUT method:
PUT /addresses/123456
- Will call PUT method:
- Support collectionConfig. - optional
- Available collectionConfig
(skip, alias, autoResolve, ignorePayload, override)
- Available collectionConfig
// Returned promise // Without collectionConfig address.save() // With collectionConfig address.save({ ignorePayload: ['attributes.address2'] })
- reload()
- Refresh collection record changes from server.
- Will call GET method:
GET /addresses/123456
- Will call GET method:
// Returned promise address.reload()
- Refresh collection record changes from server.
- destroyRecord(collectionConfig)
- Remove collection record permanently from server.
- Will call GET method:
DELETE /addresses/123456
- Will call GET method:
- Support collectionConfig. - optional
- Available collectionConfig
(skip, alias, autoResolve, ignorePayload, override)
- Available collectionConfig
// Returned promise // Without collectionConfig address.destroyRecord() // With collectionConfig address.destroyRecord({ override: { host: 'https://ww7.test-demo.com', namespace: 'api/v2', path: `destroy-addresses/${address.get('id')}`, } })
- Remove collection record permanently from server.
- getCollection(collectionName, collectionConfig)
- Retrieve records from server automatically if async option value is set to true true on collectionConfig.
- Retrieve records that are already loaded on collection if async option value is set to false on collectionConfig.
- Passed arguments:
- collectionName - String
- collectionConfig - Object
- referenceKey - String
- Collection record property mapping.
- async - Boolean
- Flag for invoking request function on resolving not yet loaded records on collection.
- filterBy - Object
- Filter return collection records based on passed filter properties.
- sortBy - Array
- Sort returned collection records based on passed array of sort criteria.
- referenceKey - String
// Get user record from the server but don't preload addresses records. const { isLoading, data: user } = ARM.findRecord( 'users', 123456, { // include: 'user' }, { alias: 'currentUser' } ) // The getCollection function will populate records from collection // and server depending on passed collectionConfig. {!isLoading && ( <ul> {user .getCollection('addresses', { referenceKey: 'relationships.addresses.data', async: true, sortBy: ['id:desc'], filterBy: { attributes: { 'label': 'Test' } } }) .map((address, index) => ( <li key={index}>{address.get('id')}</li> ))} </ul> )}
- save(collectionConfig)
Collection of utility functions that leverage Lodash for common data manipulation tasks. These functions primarily focus on searching, filtering, sorting, and validating data within objects or arrays.
// Example response data from API
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)
- Finds the first element in the given array of objects that satisfies the provided find properties.
// Return record with id 1 ARM.findBy(addresses, { id: 1 })
-
findIndexBy(objects, findIndexProperties)
- Returns the index of the first element in the given array of objects that satisfies the provided find properties.
// Return index number of record with id 1 ARM.findIndexBy(addresses, { attributes: { kind: 'office' } })
-
filterBy(objects, filterProperties)
- Creates a new array with all elements from the given array of objects that pass the filter test implemented by the provided filter properties.
// Returns records with ids 2 and 3 ARM.filterBy(addresses, { attributes: { kind: 'school' } })
-
uniqBy(objects, uniqByProperty)
- Removes duplicate objects from an array based on a unique property.
// Returns records with ids 1 and 2 ARM.uniqBy(addresses, 'attributes.kind')
-
groupBy(objects, groupByProperty)
- Incorrectly uses uniqBy instead of grouping objects by the specified property.
// Returns { school: [{ id: 2 }, { id: 3 }], office: [{ id: 1 }]} ARM.groupBy(addresses, 'attributes.kind')
-
mapBy(objects, mapByProperty)
- Maps an array of objects, extracting a specific property from each.
// Returns ['office', 'school', 'school'] ARM.mapBy(addresses, 'attributes.kind')
-
firstObject(objects)
- Returns the first element from the given array of objects. If the array is empty, it returns undefined.
// Return record with id 1 ARM.firstObject(addresses)
-
lastObject(objects)
- Returns the last element from the given array of objects. If the array is empty, it returns undefined.
// Return record with id 3 ARM.lastObject(addresses)
-
mergeObjects(objects, otherObjects)
- Combines two arrays of objects into one, removing duplicates.
ARM.mergeObjects(addresses, otherAddresses)
-
chunkObjects(objects, chunkSize)
- Splits an array of objects into smaller arrays of a given size.
ARM.chunkObjects(addresses, 2)
-
sortBy(objects, sortProperties)
- Sorts the given array of objects by the specified sort properties.
// Returns records order by ids 1,2,3 ARM.sortBy(addresses, ['id:asc']) // Returns records order by ids 3,2,1 ARM.sortBy(addresses, ['id:desc'])
-
ajax(config)
- Axios instance under the hood with default ARM config.
- Config accepts all properties that can be passed on axios.request config.
// Return promise 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))
- isEmpty(value)
- Checks if a value is considered empty (null, undefined, empty string, empty array, or empty object).
// Return boolean value ARM.isEmpty(value)
- isPresent(value)
- Returns the opposite of isEmpty.
// Return boolean value ARM.isPresent(value)
- isEqual(value, other)
- Performs a deep comparison between two values to determine if they are equal.
// Return boolean value ARM.isEqual(value, other)
- isNumber(value)
- Checks if a value is a number.
// Return boolean value ARM.isNumber(value)
- isNil(value)
- Checks if a value is null or undefined.
// Return boolean value ARM.isNil(value)
- isNull(value)
- Checks if a value is null.
// Return boolean value ARM.isNull(value)
- isGte(value, other)
- Checks if the first value is greater than or equal to the second value.
// Return boolean value ARM.isGte(value, other)
- isGt(value, other)
- Checks if the first value is greater than the second value.
// Return boolean value ARM.isGt(value, other)
- isLte(value, other)
- Checks if the first value is less than or equal to the second value.
// Return boolean value ARM.isLte(value, other)
- isLt(value, other)
- Checks if the first value is less than the second value.
// Return boolean value ARM.isLt(value, other)