Your company has recently launched its API and is looking to cultivate a user community around it. Given that most of your customer base will be working with JavaScript due to the API’s focus on simplifying web application development (similar to Twilio), it’s crucial to provide them with convenient tools.
While your RESTful API might be straightforward, you understand the need for something more developer-friendly than raw API calls. users are going to want to drop in a JavaScript package
Therefore, you’re developing a library, or perhaps a state management system for a web application that interacts with your internal API. In either case, minimizing repetitive code when performing CRUD operations on API resources is essential for maintainability and efficiency.
This is where ActiveResource.js comes in – a JavaScript ORM designed for seamless API interactions. Inspired by a project requirement to build a JavaScript SDK rapidly, it prioritizes conciseness and developer productivity.
ActiveResource.js draws inspiration from Ruby on Rails’ ActiveRecord ORM, adhering to the principles of “convention over configuration” and “exalt beautiful code.”
JavaScript SDK Principles
ActiveResource.js incorporates two key Ruby on Rails concepts:
- “Convention over configuration”: Assumptions are made about API endpoint structure, such as a
/productsendpoint for aProductresource, reducing configuration overhead and allowing developers to incorporate new API resources efficiently. - “Exalt beautiful code”: As Rails’ creator DHH said it best— ActiveResource.js simplifies complex requests, abstracting away JSON manipulation and providing an elegant syntax for filtering, pagination, and managing nested relationships.
Before We Start
Currently, ActiveResource.js primarily supports APIs adhering to the JSON:API standard specification.
Resources for setting up a JSON:API server are readily available if you’re unfamiliar with the standard and wish to follow along. many good libraries
However, it’s worth noting that ActiveResource.js’s adaptability extends beyond a single API standard. Its interface can be tailored for custom APIs, which might be explored in future articles.
Setting Things Up
Begin by integrating active-resource into your project:
| |
Next, create a ResourceLibrary for your API, placing your ActiveResources in a dedicated directory, such as src/resources:
| |
The createResourceLibrary function primarily requires the base URL of your API.
Building a CMS SDK
Let’s demonstrate ActiveResource.js by constructing a JavaScript SDK for a content management system API. Our CMS will feature users, posts, comments, and notifications.
Users will have the ability to manage posts (read, create, edit), engage with comments (read, add, delete), and receive notifications for new posts and comments.
For this tutorial, we’ll keep the focus solely on API interaction through ActiveResources, abstracting away specific view libraries (React, Angular, etc.) or state management solutions (Redux, etc.).
Starting with Users
We’ll commence by establishing a User resource to handle user management within the CMS.
Initially, let’s define a User resource class along with some basic attributes:
| |
For authentication, let’s assume you have an endpoint (/auth for instance) that handles user login (email and password submission) and returns an access token along with the user’s ID. A function named requestToken will manage this endpoint. Once authentication is successful, we want to retrieve the user’s complete data:
| |
Here, library.headers is set to include an Authorization header with the accessToken. This ensures that subsequent requests made by the ResourceLibrary are authenticated.
The final step in authenticate involves a request to User.find(id), which queries the API endpoint /api/v1/users/:id. The response might resemble:
| |
The authenticate function will return an instance of the User class. You can then access the authenticated user’s attributes (e.g., for display purposes) using camelCase notation, aligning with JavaScript conventions.
| |
You can retrieve individual attributes as object properties or obtain all attributes using user.attributes().
Implementing a Resource Index
Before proceeding with related resources (e.g., notifications), it’s beneficial to introduce a resource index file (src/resources/index.js). This serves two primary purposes:
- Streamlined imports: Allows destructuring multiple resources from
src/resourcesusing a single import statement. - Resource initialization: Facilitates the initialization of all resources within the
ResourceLibraryby callinglibrary.createResourcefor each resource, which is necessary for ActiveResource.js to establish relationships between resources.
| |
Introducing Related Resources
Now, let’s incorporate a related resource for User – Notification. Start by defining a Notification class that belongsTo the User class:
| |
Subsequently, include it in the resource index:
| |
Next, establish the relationship between notifications and the User class:
| |
With this setup, after retrieving the user through authenticate, you can effortlessly load and display their notifications:
| |
Alternatively, notifications can be included directly in the initial request for the authenticated user:
| |
This showcases the flexibility of the provided DSL.
Exploring the DSL
Let’s review the capabilities of the DSL we’ve constructed so far.
You can query both collections and individual users:
| |
Furthermore, you can refine queries using chainable relational methods:
| |
Observe how queries can be composed using multiple chained modifiers and terminated with methods like .all(), .first(), .last(), or .each().
You have the option to create user instances locally or persist them on the server:
| |
Once a user is persisted, modifications can be sent to the server for updates:
| |
Deletion from the server is equally straightforward:
| |
This fundamental DSL extends to related resources as well, as we’ll illustrate while developing the remaining CMS components: posts and comments.
Creating Posts
Define a Post resource class and associate it with the User class:
| |
| |
Remember to include Post in your resource index:
| |
Now, let’s integrate the Post resource with a form for creating and editing posts. When accessing the form, a new Post instance is created. Changes in the form are reflected in the Post object:
| |
Next, implement an onSubmit handler for the form to persist the post to the server, incorporating error handling:
| |
Editing Existing Posts
After saving a post, it’s linked to your API as a server-side resource. You can determine if a resource exists on the server using the persisted property:
| |
ActiveResource.js supports the concept of “dirty attributes” for persisted resources, allowing you to track modifications since the last server synchronization.
Invoking save() on a persisted resource with changes triggers a PATCH request, transmitting only the modified attributes instead of the entire resource data.
Let’s track changes to the post.content attribute:
| |
With a persisted post, you can now implement editing functionality. The submit button can be conditionally enabled/disabled based on attribute changes:
| |
Methods are available for managing singular relationships, such as post.user(), if you need to modify the user associated with a post:
| |
This is analogous to:
| |
The Comment Resource
Next, create a Comment resource class and establish its relationship with Post. Since comments can be replies to either posts or other comments, we’ll use a polymorphic association:
| |
Ensure that Comment is included in /src/resources/index.js.
A slight addition to the Post class is required:
| |
The inverseOf option in the hasMany definition for replies specifies that this relationship is the inverse of the polymorphic belongsTo definition for resource. The inverseOf property is crucial for establishing proper associations, especially for polymorphic relationships where automatic determination might not be accurate.
Managing Post Comments
The same DSL applies seamlessly to managing related resources. With our post-comment relationships in place, we can perform various operations:
Adding a new comment to a post:
| |
Adding a reply to an existing comment:
| |
Modifying a comment:
| |
Deleting a comment from a post:
| |
Displaying Posts and Comments
Our SDK can power the display of paginated posts. Clicking a post loads a new page with the post and its associated comments:
| |
This query retrieves the 10 most recent posts, optimizing the request by fetching only the content attribute.
Pagination can be implemented by handling user interactions (e.g., clicking a “Next Page” button) and dynamically loading subsequent pages:
| |
When a post link is clicked, you can load and display the full post data, including comments and replies:
| |
Calling .target() on a hasMany relationship (e.g., post.replies()) provides an ActiveResource.Collection containing loaded comments.
This is important because post.replies().target().first() accesses the first loaded comment, whereas post.replies().first() initiates a request to fetch the first comment from the server (GET /api/v1/posts/:id/replies).
Replies for a post can also be requested separately, allowing for more specific queries. Modifiers like order, select, includes, where, perPage, page can be applied when querying hasMany relationships, similar to querying resources directly.
| |
Modifying Retrieved Resources
There are cases where you might need to modify data retrieved from the server before utilizing it. For example, you could wrap post.createdAt in a moment() object to present a user-friendly date and time representation:
| |
Immutability
ActiveResource.js can be configured to work with immutable objects, often preferred in state management systems, by adjusting the resource library settings:
| |
Integrating Authentication
To conclude, let’s integrate your authentication system with the User ActiveResource.
Assume your token-based authentication endpoint is /api/v1/tokens. Successful authentication returns the user’s data along with the authorization token.
Create a Token resource class associated with User:
| |
Include Token in /src/resources/index.js.
Next, add a static authenticate method to your User resource class, and relate User to Token:
| |
This method utilizes resourceLibrary.interface() (JSON:API in this case) to send user data to /api/v1/tokens. The request would look like this:
| |
The response includes the authenticated user and the authentication token:
| |
We then set the library’s Authorization header using token.id and return the user object, effectively replicating the behavior of User.find().
Now, User.authenticate(email, password) returns an authenticated user, and subsequent requests are authenticated using the access token.
Conclusion
ActiveResource.js simplifies JavaScript SDK development for managing API resources and their relationships. For further exploration, refer to the comprehensive documentation available README for ActiveResource.js.
This library aims to streamline your development process, and contributions are always welcome!