Last week I published an article outling how I rewrote an existing Express API using Loopback, resulting in 75% less code. I described how we initially utilized the topcoder community to design and build the API and the benefits of LoopBack versus simply using Express and Mongoose. In this post, I'll walk through the process of building the "TopBlogger" API with LoopBack and where it saved time and made code obselete.
I'll trying cover the application from the ground up, but you might want to peek at the previous post for the complete details. You can find all of the code referenced in this article at this repo.
Requirements
The TopBlogger application has two models: Blog and Comment. We'll be using the LoopBack provided User object for authentication and authorization but you can extend this if you'd like more functionality. It works just fine out of the box for what we need. The models have the following relations:
- A blog belongs to a user
- A blog can have many comments
- A comment belongs to a user
The functional requirements for the blogging API are fairly straight forward. When blogging, everyone can typically view blogs and comments but any meaningful interaction requires authentication. Once logged in users can create new blogs, edit blogs that they authored, delete an unpublished blog that they authored, up or down-vote blogs not authored by themselves, comment on blogs and like or dislike comments not authored by themselves. No one is allowed to delete comments. That would just be crazy.
Given that people want to find and read blog entries, a fair amount of work is needed for discovery. We need endpoints for keyword search, newest blogs, trending blogs, most popular blogs and blogs by author and tags. All of this of course with pagination. We also want permalinks so that people can access a blog by the author username and slug (e.g., http://topblogger.com/jeffdonthemic/hello-world).
Scaffold the Application
The first thing we need to do is install the LoopBack CLI. Run npm install -g strongloop
, then get up and grab a cup of coffee, watch some cat videos or take a run. The install takes a while.
Now we are ready to create a new application. Run slc loopback
to start the generator and enter topblogger
for the name of the application and directory. This will scaffold the API and run npm install. To smoke test just change to the new directory and run node .
You should see the running application at http://localhost:3000!
Add MongoDB Database
LoopBack offers a number of data providers, in memory being the simpliest, but we're going to be using MongoDB. We can use the Data source generator to set this up for us.
Enter topblogger
as the name and choose the MongoDB connector. Next we'll need to add our connection parameters. Assuming you are running MongoDB locally, edit your server/datasources.json
file so that it looks something like:
Defining Models
Models are at the heart of LoopBack. When you connect a model to a persistent data source, LoopBack implements all of the CRUD operations needed to interact with the database and exposes the REST endpoints automatically. No need to write handlers for each endpoint! We can then add application logic to models, boot scripts or middleware to round out our functionality!
Use the LoopBack model generator to build the Blog and Comment models below.
Follow the prompts to create the properties below. Create the Comment model the same way along with its properties. It's not rocket surgery.
Blog
- title (string)
- content (string)
- tags (array of strings)
- slug (string)
- numberOfUpVotes (number)
- numberOfDownVotes (number)
- numberOfViews (number)
- upvotes (array of strings)
- downvotes (array of strings)
- isPublished (boolean)
- createdDate (date)
- lastUpdatedDate (date)
Comment
- content (string)
- numOfLikes (number)
- numOfDislikes (number)
- likes (array of strings)
- dislikes (array of strings)
- createdDate (date)
- lastUpdatedDate (date)
Create the Relations
One of the great things about Rails is the functionality to easily implement and use relations between models. LoopBack offers similar functionality. The framework offers BelongsTo, HasMany, HasManyThrough, HasAndBelongsToMany, Polymorphic and Embedded relations that allow you to connect, query, expose endpoints and perform all sorts of nifty functional with models.
Since users write blogs, create a BelongsTo relation to User to define the "author" of each Blog.
Readers love commenting on blogs so we need to allow a blog to have many comments. Create a HasMany relation so that we can associate an array of Comments to a Blog.
And lastly we'll need to create the same type of author relation for Comments that we did for the Blog above. Every comment must be authored by a user.
Implementing Access Control
One of the most powerful features of LoopBack is access control. LoopBack applications access data through models, so controlling access to data means putting restrictions on models. LoopBack access controls are determined by access control lists or ACLs which specify who or what can read or write data and execute methods. Our application only consists of the two following types of users:
Unauthenticated
Unauthenticated users can only view blogs and comments.
Authenticated
LoopBack provides all sorts of user related functionality for us like signup, login, logout, forgot password and much more. We'll gladly use this! Once logged in, users have the same functionality as unauthenticated user plus the ability to:
- Create a new blog
- Edit blogs where they are the author
- Publish a blog where they are the author
- Upvote a blog where they are not the author
- Downvote a blog where they are not the author
- Create a comment for a blog
- Edit a comment where they are the author
- Like a comment where they are not the author
- Unlike a comment where they are not the author
We start by denying access to all endpoints to everyone for Blogs and Comments. Then we'll go through and authorize select endpoints where appropriate.
Allow everyone read access to Blogs and Comments.
Only authenticated users can create new Blog records.
Make sure that only a Blog's author can edit and publish it. The publish
method is a custom remote method that we'll add to the Blog model later.
We'll define upvote
and downvote
remote methods later but we'll add the security now by first blocking all access to the endpoints and then letting any authenticated user have access to them. We also have a requirement that users cannot upvote/downvote their own Blog but we'll handle that outside of the ACL in code.
And finally, we'll specify that only authenticated users can create and edit Comments.
Now that were done, take a peek at Blog and Comment JSON files in common/models
to see how LoopBack generated the ACL section.
Adding Custom Remote Methods
LoopBack exposes models endpoints automatically but at some point you'll want provide additional functionalty besides just CRUDing records. LoopBack makes this extremely simple with remote methods. For the Blog model we need to expose /blogs/:id/publish
, /blogs/:id/upvote
and /blogs/:id/downvote
endpoints. I'll just touch on the important pieces so be sure to check out the /common/models/blog.js file for all of the code.
We start off the remote method by register a PUT method called publish
that accepts the blog's ID in the query string and returns the updated blog object. This code also sets up the method in the Swagger Explorer for easy testing. The Blog.publish
function handles the actual request for the endpoint. It simply finds the blog record in MongoDB, sets a few properties and commits it. The security for the endpoint was setup earlier in the ACL.
The upvote
remote method is similar but has a little more substance to it. This time we register a POST method that, again, accepts the blog's ID in the query string and returns a the updated blog object. However, we added a remote hook that runs before the remote method is called. If the caller is either the author of the blog or has already upvoted the blog, then it returns a 403 error. Else it executes the remote method to increment the number of upvotes and adds the caller to the array of users that upvoted the blog.
Removing Functionality
With LoopBack its super simply to hide methods and endpoints. Since we don't want anyone to be able to delete comments, we simply add the following to the Comment model:
Adding Custom Express Routes
Our last requirement is enable permalinks so that people can access a blog by the author's username and slug (e.g., http://topblogger.com/jeffdonthemic/hello-world) instead of by Blog ID. However, since the endpoint is not directly tied to a model we'll need to use a boot script to add our new custom route.
The /server/boot/routes.js file holds all of the custom routes (all one of them) and is loaded when the applications starts. Looking very similar to an Express route (because it is), this GET method uses the user and slug values from the query string, finds the record and its associated comments in MongoDB and returns it the same way it would if fetched by Blog ID.
Code Comparison
So just exactly where did LoopBack save us time? Well, for instance, the LoopBack blog model code looks much more succinct and pleasing to the eye than the original blog model code. The original code handles CRUD functional and is explicitly checking for access and returning status codes (400, 401, 403, 404, etc.) that LoopBack handles. My custom code decreased from 1,272 lines to 314 lines! Wow!
Testing was much simplier with LoopBack. Our mocha tests went from 1,599 lines to 380 lines. This substantial reduction in test code will make life much easier in the future for enhancements and debugging! Based upon my requirements, I simply set up my tests for both unauthenticated and authenticated users.
- The original authorization test code was no longer needed. LoopBack provides this out of the box.
- The original discovery test code to find blog was no longer needed. LoopBack has elegant support for finding data; related or not!
- Most of the blog and comment model test code was no longer needed.
Conclusion
LoopBack is great for building APIs and it's a huge productivity gain. However, coupled with the other services StrongLoop offers, it's an impressive suite of tools with which to build applications.