List of Figures
Chapter 1. Getting started
Figure 1.1. The contents of a response from an initial request by your browser to https://brushfire.io
Figure 1.2. The frontend makes a request and the backend makes a response.
Figure 1.3. Clicking the Sign Up button generates a request to the Sails backend, which responds with the signup page.
Figure 1.4. The components necessary for the backend to satisfy the request and deliver the signup page to the frontend
Figure 1.5. The components necessary for the backend to satisfy the request by creating a user and responding with the result
Figure 1.6. Understanding how the Sails router matches the incoming request with routes to trigger an action
Figure 1.7. The router matched the request as part of an explicit route.
Figure 1.8. A route operates like a click event handler . The route address serves a similar purpose to the combination
of the DOM selector and DOM event. And the route target is similar to an event handler function .
Figure 1.9. An example of the URL query string that starts with a question mark, contains a key/value pair separated by an
equals sign, and is separated by an ampersand
Figure 1.10. The components of a model include attributes, methods, and settings.
Figure 1.11. An endpoint from a backend API in action, processing a request from a signup form
Figure 1.12. The frontend-first approach to backend development
Figure 1.13. An interactive mockup of a signup form. The fields in this form help determine the request parameters you should
expect when designing the API endpoint to process it.
Figure 1.14. An annotated mockup showing the requests this user interface will send to your Sails app
Figure 1.15. As long as the expected request and response for an API endpoint remain consistent, frontend code doesn’t need
to change.
Figure 1.16. Examples of frontend user-agents used in web applications
Figure 1.17. Sails used as a pure API with no responsibilities for delivering frontend assets
Figure 1.18. Typical frontend requests to the backend using the SPA approach
Figure 1.19. Delivering personalized HTML and static assets to a hybrid web application
Figure 1.20. The two security realms of a web application
Chapter 2. First steps
Figure 2.1. Comparing the code of two different projects in Sublime Text with ShiftIt
Figure 2.2. The terminal consists of one or more windows or tabs. Each window or tab contains a command line, where you type
commands.
Figure 2.3. The Node installation page for Windows, OS X, and Linux operating systems
Figure 2.4. npm consists of a registry with hundreds of thousands of packages , a command-line-interface application , and
individual packages .
Figure 2.5. The globally installed version of Sails generated a new Sails application and installed itself as a dependency
of the new project .
Figure 2.6. The Sails server up and running
Figure 2.7. The JSON response from the purpose action.
Figure 2.8. An overview of the GET request to /life/purpose
Figure 2.9. The terminal window responds with the command prompt after you close the Sails server with Ctrl-C.
Figure 2.10. Only one node application at a time can be running on a particular port.
Figure 2.11. A remote GitHub repository with links to all files in the repo as well as a link to clone the repo
Figure 2.12. The cursors Sails project in action
Chapter 3. Using static assets
Figure 3.1. Delivering assets and endpoints to a single-page application
Figure 3.2. After starting the Sails server and navigating to the root route of the project, Sails responds with the default
homepage.
Figure 3.3. In addition to an action, an explicit route can have a view as its target . When a request matches the root
address of this route, the Sails server responds with a view . In this case, the view is the homepage .
Figure 3.4. The homepage before and after running the static generator
Figure 3.5. Using a server-rendered view, the path is added before the response is sent to the requesting user-agent. Using
a static asset, the index.html page isn’t changed between the request and response.
Figure 3.6. The static-asset pipeline aggregates preprocessing tasks to assets.
Figure 3.7. The brushfire/assets/ folder synced with the brushfire/.tmp/ public/ folder
Figure 3.8. The videos.html file synced in both the assets/ and .tmp/public/ folders
Figure 3.9. Interactive mockups are an essential part of the frontend-first approach to development. Each chapter has an online
hub that contains mockups. The homepage mockup from the chapter 3 hub is pictured here.
Figure 3.10. The homepage with Bootstrap styling added as a CDN dependency
Figure 3.11. The videos page mockup from the Sails.js in Action hub
Figure 3.12. A GET request to /videos triggers a matching asset route, which displays index.html from brushfire/assets/videos/index.html.
Figure 3.13. The homepage with an image in the navbar using a relative path
Figure 3.14. The source path within the image tag used a relative path that didn’t exist and resulted in a broken link.
Figure 3.15. The page source reveals the reference links to the Bootstrap and importer.css files.
Figure 3.16. By default, an importer.less file is generated for each new Sails project. You’ll see later how importer.less
is compiled into importer.css. What’s important here is that the compiled importer.css file was injected into brushfire/assets/index.html
because it contains the special Sails linker tags. After adding bootstrap.min.css to the brushfire/assets/styles folder,
a link was also automatically added to brushfire/assets/index.html for the bootstrap.min.css file.
Figure 3.17. The page source reveals the reference script tag to the sails.io.js file.
Figure 3.18. By default, a sails.io.js file is generated by each project and placed in the brushfire/assets/js folder. sails-linker
inserts a reference to it in brushfire/assets/index.html .
Figure 3.19. The homepage with the new styles in custom.less
Figure 3.20. The videos page has two requests, one for new video submissions and the other for the initial videos of the page.
Figure 3.21. The added video from the video-submit-form using jQuery
Chapter 4. Using the blueprint API
Figure 4.1. The backend API endpoint is triggered by a request from the frontend . The Sails router attempts to match the
route , and if it finds a match, executes an action . The action does stuff and responds to the frontend .
Figure 4.2. The video page can send two requests: one to post new video submissions and the other to get the initial videos
of the page.
Figure 4.3. Choosing between safe, alter, or drop auto-migration in Sails
Figure 4.4. The shortcut blueprint route triggered the create blueprint action, which created a new video record in the video
model and responded with the record’s contents as JSON.
Figure 4.5. The Sails router listens for an incoming request B and tries to match it to an explicit route. Not finding a match
with an explicit route, it next tries to match it to a shortcut blueprint route c. In this example it finds a match, executes
the create blueprint action d, and e responds with a 200 status code and the newly created video record as JSON.
Figure 4.6. The start of the query string uses a question mark (?) followed by field/value pairs where each pair is separated
by an equals sign (=) and the field/value pairs are separated by an ampersand (&).
Figure 4.7. find blueprint actionid parameterThe shortcut blueprint route uses the find blueprint action and responds with
a list of records from the video model.
Figure 4.8. When you add an :id parameter to the URL, in this case /3, the response is a single record from the video model.
Figure 4.9. The shortcut blueprint route uses the update blueprint action and responds with the record containing the updated
title.
Figure 4.10. The shortcut blueprint route uses the destroy blueprint action and responds with the deleted video record.
Figure 4.11. The steps in a RESTful blueprint route include recognition of the incoming request by the Sails router , matching
the request to a route , and executing the find blueprint action , which queries all video records and responds with an array
of video record dictionaries.
Figure 4.12. The page triggers the RESTful blueprint route and the find blueprint action and then responds with all records
in the video model.
Figure 4.13. POST methodThe RESTful route and a create action were triggered by the Angular AJAX POST request, and the action
responded with the newly created record of the video.
Figure 4.14. The RESTful blueprint route and a create action created a new video record based on a POST request to /video.
Figure 4.15. Here, you use Postman to make a GET request to the /video/2 path . The API responds with a JSON dictionary of
the particular video record .
Figure 4.16. Here, you use Postman to make a PUT request to the path /video/2 with the URL parameter title=Ludacris . The
API responds with a JSON dictionary of the updated video record .
Figure 4.17. Here, you use Postman to make a a DELETE request to /video/2 that contains the record id that you want to delete.
The API responds with the JSON video record that was deleted .
Figure 4.18. Replacing the AJAX GET request with the Sails WebSockets GET enhances the functionality of the find action.
Figure 4.19. When the io.socket.post() method made a POST request to /video, the create action added the new video record
that emitted a video event. This event was received by the previously added event handler, which triggered an update to the
DOM with the new video record.
Chapter 5. Custom backend code
Figure 5.1. After you started the Sails server, the bootstrap.js file was executed, in which your code logged Hello World!
to the terminal. Then, you passed control back to Sails by calling cb(). At that point, Sails went about its business, completing
the process of lifting the server.
Figure 5.2. The .count() method calculates the number of records in the video model, and then, depending on the result, your
code logs the appropriate message. Either way, you pass control back to Sails and it finishes lifting your app.
Figure 5.3. The page for machinepack-youtube provides a list of methods, called machines. In this case, all the machines are
related to the YouTube API.
Figure 5.4. The .searchVideos() machine page includes example code that you can copy and paste into your own project, the
inputs expected by the machine, including their data types and whether they’re required or optional, and what you can expect
when the machine exits.
Figure 5.5. The main page for machinepack-youtube on the npmjs registry
Figure 5.6. The terminal window might display the number of existing records in the video model. But if no records exist,
you’ll see this error because of a bug in the example code.
Figure 5.7. The .searchVideos() machine takes three inputs: the query to use when searching YouTube , a Google developer API
key , and an optional limit on how many videos to return .
Figure 5.8. Your shiny new API key is displayed after completing the key-creation process in the Google Developers Console.
Figure 5.9. Node-Machine.org provides details about the exits of each machine in every open source machinepack on npm.
Figure 5.10. The data returned by the YouTube API is not in the format that the frontend requires .
Figure 5.11. The transformed data from YouTube, now safely stored as new video records and displayed by the frontend
Chapter 6. Using models
Figure 6.1. The four components of an identity, authentication, personalization, and access control system
Figure 6.2. Model definitions consist of attributes, methods, and settings. In addition, every model is connected with a particular
adapter.
Figure 6.3. Clone the repo using the URL from the main repo page for the start of chapter 6.
Figure 6.4. Current organization of the interactive mockups. The profile, edit-profile, and restore-profile pages haven’t
yet been connected via links.
Figure 6.5. The signup page contains four user model attributes: email, username, password, and confirmation.
Figure 6.6. The profile page contains an additional user model attribute we’ve named gravatarURL. This attribute will store
the URL linking the Gravatar image.
Figure 6.7. The User Administration page adds two user model attributes: admin and banned.
Figure 6.8. The restore-profile page allows a user to remove the deleted state of their profile. This will necessitate adding
a deleted attribute to the user model.
Figure 6.9. Clicking this button sends a POST request to /user.
Figure 6.10. The Sails router listens for an incoming request, matches it to a RESTful blueprint route, executes the create
blueprint action, and responds with a 200 status code and the newly created user record as JSON.
Figure 6.11. This is the result of the blueprint POST request to /user and the redirect to localhost:1337/user/1. In addition
to the input fields that were added to the record, createdAt, updatedAt, and id were also added.
Figure 6.12. Sails looks for the database connection it will use to store and manipulate records for a particular model in
brushfire/api/models/User.js . If it doesn’t find a connection, it then looks to model settings . If no connection exists,
Sails looks to the internal core default connection, localDiskDb . The default connection uses the sails-disk adapter to
access the sails-disk database.
Figure 6.13. The sails-disk database located in brushfire/.tmp/local-DiskDb.db reveals the first user record and the first
video record .
Figure 6.14. When you attempt to create a user, a record with an identical email address exists in the database and therefore
produces a validation error.
Figure 6.15. Using the blueprint actions, all parameters are returned to the frontend.
Figure 6.16. The generic syntax of a model method includes the model name , model method , criteria , values , the query method
, the callback method with error and result arguments , and the callback body .
Figure 6.17. The create model method uses a syntax similar to the find, update, and destroy methods, but without criteria.
Chapter 7. Custom actions
Figure 7.1. The four components of an identity, authentication, personalization, and access control system
Figure 7.2. An explicit route contains a route address and a target.
Figure 7.3. You made a GET request to /user/hello , but there’s something wrong with the action because the loading message
displays and then times out.
Figure 7.4. The message from the custom action is logged to the console.
Figure 7.5. Again, you make a GET request to /user/hello . Now that you’ve added a response in the action, Postman receives
the response and displays it .
Figure 7.6. You can use these mockups to identify the requests on each page as well as the requirements and expectations of
each endpoint.
Figure 7.7. The signup page mockup contains three endpoints. The first request initiates the display of the signup page, which
is handled by an asset route. The second request is a link to the restore-profile page that’s handled by an asset route, and
the third is a POST request to /user/signup that will be handled by a custom controller action.
Figure 7.8. The incoming POST request to /user matched a blueprint RESTful route that triggered a blueprint create action
that ultimately created the user record .
Figure 7.9. Using Postman, create a new user with the custom signup action. This will be a POST request to localhost:1337/user/signup
with an email parameter set to [email protected] . The signup action responds with the email address as JSON and a 200
status code .
Figure 7.10. The Emailaddresses.validate() machine page provides example usage.
Figure 7.11. Using Postman, check the validity of the email attribute in the custom signup action. Generate a POST request
to /user/signup with an invalid email parameter , with the username and password parameters also provided. The signup action
executes the invalid exit and responds with a 400 code and message .
Figure 7.12. You’ll use the Passwords.encryptPassword() machine to encrypt a user’s password before it’s stored in the user
record.
Figure 7.13. Using Postman, make a POST request to /user/signup and pass the password parameter to the action , which will
respond with the encrypted password .
Figure 7.14. Using Postman, make a POST request to /user/signup and pass the email parameter to the action , which will respond
with the gravatarURL .
Figure 7.15. This is an example of the .create() model method of the user model . The attributes of the record to be created
are passed as an argument to the method as a dictionary {} or an array of dictionaries [{}]. The .exec() method is also referred
to as a deferred function. Using .exec() will give you the flexibility to chain modifier methods together and to make the
usage easier to read. For now, you'll use .exec() with an anonymous function as the callback. Within this callback, you’ll
pass any errors as the first argument and the resulting data from the method . You can then use the body of the callback
to respond, or continue with another task.
Figure 7.16. The POST request to localhost:1337/user/signup passes in the email, username, and password parameters , and
the action responds with the created record .
Figure 7.17. The frontend displays an error dictionary returned by the User.create() model method that indicates that one
of the unique validations has been violated.
Figure 7.18. The signup page now reflects the appropriate error messages when a duplicate email or username attribute is attempted.
Figure 7.19. The profile page mockup contains four endpoints. The first request initiates the display of the profile page
and is handled by an asset route. The second request is a GET request to /user/profile/:id for the initial profile information
on the page. The third request is triggered by the Edit button to GET the edit-profile page and is handled by another asset
route. The final request is a DELETE request to /user/:id and is handled by a custom controller.
Figure 7.20. The findOne model method consists of the model name , the model method , criteria , the query method , the callback
method with error , the callback body , and result arguments .
Figure 7.21. The restore-profile page mockup contains two endpoints. The first request initiates the display of the restore-profile
page and is handled by an asset route. The second request is a PUT request to /user/ restoreProfile and is handled by a custom
action.
Figure 7.22. The edit-profile page mockup contains five endpoints. The first request initiates the display of the edit-profile
page and is handled by an asset route. The second request is a GET request to /user/profile/:id for the initial profile information
on the page. The third request is triggered by the Restore Gravatar URL With Current Email Address button and is a PUT request
to /user/restoreGravatarURL, which is handled by a custom action. The fourth request is a PUT request to /user/updateProfile
and is also handled by a custom action. The final request is a PUT request to /user/changePassword and is handled by a custom
action.
Figure 7.23. The administration page mockup contains five endpoints. The first request initiates the display of the administration
page and is handled by an asset route. The second request is a GET request to /user/ adminUsers for the profile information
of all users. The third request is a toggle to the admin attribute that triggers a PUT request to /user/updateAdmin, which
is handled by a custom action. The fourth request is a PUT request to /user/updateBanned and is also handled by a custom action.
The final request is a PUT request to /user/updateDeleted and is handled by a custom action.
Chapter 8. Server-rendered views
Figure 8.1. You want to show different assets based on the user authenticated state. Here, if the user is not logged in, you’ll
display the log-in form . If the user is logged in, you’ll display their Gravatar image, their email address, and a sign-out
button .
Figure 8.2. The four components of an identity, authentication, personalization and access control system
Figure 8.3. Delivering assets and endpoints to a single-page application
Figure 8.4. In a single-page app, the entry page is the anchor page where templates are inserted based on frontend routes
and the Angular router.
Figure 8.5. Delivering assets and endpoints to a hybrid web application
Figure 8.6. Using custom routes and backend Sails routing to manage page navigation in Brushfire
Figure 8.7. An incoming GET request to / matches an explicit route that triggers a view named homepage.ejs to be rendered
into homepage.html and sent as a response to the requesting user-agent.
Figure 8.8. An incoming GET request to / B matches an explicit route and passes a locals dictionary c to a view named homepage.ejs
d. The view contains a special tag that, when rendered, inserts a value from the locals dictionary as a response e to the
requesting user-agent.
Figure 8.9. How the Sails router matches the incoming request with routes to trigger an action
Figure 8.10. An explicit route is a JavaScript dictionary made up of a route address and a target.
Figure 8.11. The layout file contains the navigation partial and surrounds the view that’s being rendered.
Figure 8.12. The new navigation section contains both the authenticated and unauthenticated markup states.
Figure 8.13. The homepage view with the sign-in form displayed in its unauthenticated state
Figure 8.14. Using locals through a custom route to bootstrap data in a server-rendered view
Figure 8.15. The homepage view with the simulated authenticated state displaying the authenticated markup
Figure 8.16. The relationship between the layout view and the other views of Brushfire
Figure 8.17. This videos page simulates an unauthenticated user and therefore doesn’t have the markup to add a video.
Chapter 9. Authentication and sessions
Figure 9.1. The four components of an identity, authentication, personalization, and access control system
Figure 9.2. The layout navigation partial has a sign-in form that executes a PUT request to /login when the sign-in button
is clicked.
Figure 9.3. A successful challenge to the claim of identity of [email protected]
Figure 9.4. The session consists of a session cookie and the user-agent, the session database, and a process for using a value
stored on the session cookie to securely identify each request with a particular user-agent.
Figure 9.5. You started off using client-side routing before transitioning to custom backend routes serving server-rendered
views. Now that you have the user’s authenticated state, you can move to a combination of custom routes that trigger a custom
action for each view.
Figure 9.6. When the user makes a GET request to /, it will be matched with a custom route that triggers an action that determines
the user’s authenticated state and sends the appropriate locals to the layout view, which are then added to the frontend via
an Angular controller and displayed based on a directive in the view.
Figure 9.7. Examples of how pathToView is resolved based on the configuration used
Figure 9.8. The browser was redirected to the videos page, but the navigation partial isn’t displaying the markup for the
authenticated state.
Figure 9.9. The Brushfire mockup pages with custom routes
Chapter 10. Policies and access control
Figure 10.1. Brushfire controller action list with corresponding custom routes
Figure 10.2. The videos page uses the blueprint find action to fulfill a request for all videos in the video model when the
page loads.
Figure 10.3. When the user-agent is authenticated, the videos page adds the submit-video form B, which, when clicked, triggers
the blueprint create action.
Figure 10.4. Making a POST request to /video with an unauthenticated user-agent returns a 403 forbidden status code and
message .
Figure 10.5. The homepage when accessed via an authenticated user-agent has two actions that need protection: the showSignupPage
action and the login action .
Figure 10.6. The homepage when accessed via an unauthenticated user-agent with endpoints that trigger three different controller
actions
Figure 10.7. The profile page, when accessed via an authenticated user-agent, adds two unprotected actions.
Figure 10.8. The edit-profile page, when accessed via an authenticated user-agent, adds three unprotected actions.
Figure 10.9. The signup page when accessed via an unauthenticated user adds two unprotected actions: showRestorePage and
signup .
Figure 10.10. The restore-profile page, when accessed via an unauthenticated user-agent, adds one unprotected action.
Figure 10.11. The administration page, when accessed via an authenticated user-agent whose admin property is set to true,
adds four unprotected actions.
Chapter 11. Refactoring
Figure 11.1. The navigation markup has five basic states based on the conditions of the user-agent: unauthenticated , authenticated
without the add tutorial button displayed , authenticated with the add-tutorial button displayed , authenticated and an admin
, and authenticated and an admin with the add tutorial button displayed .
Figure 11.2. Sign In and Sign Up now have standalone views.
Figure 11.3. The navigation bar adds a drop-down when the user-agent is authenticated to allow access to the user profile
and the ability to log out.
Figure 11.4. The API Reference documents each request’s friendly name , description , incoming parameters (if any) , view
(if any) , locals , method and URL path , controller and action , response type , and frontend controller (if any) .
Figure 11.5. The wireframes contain documentation for the attributes and requests of each view.
Figure 11.6. The login request has three different response codes if the user-agent is deleted , banned , or successful .
Figure 11.7. The post-pivot home/search page
Figure 11.8. The browse-tutorials page
Figure 11.9. The User Administration page
Figure 11.10. The profile page with tutorials
Figure 11.11. The profile page with users followed
Figure 11.12. The profile page with users following
Figure 11.13. The first example illustrates the impact of the route order on other single-segment routes when using the :username
variable. A GET request to /logout would be matched with the GET /:username route, which isn’t the intended result. In the
second example , we placed the GET /:username route below the route to the GET /logout route. The GET request to /logout was
matched with the route that contained the GET /logout path, which is what was intended. Note that the two-segment route to
GET /profile/edit was unaffected.
Figure 11.14. The notFound.js response
Figure 11.15. The model and attribute for each view are shown if they’re not duplicated in the user-agent authenticated and
tutorial-owner views . Therefore, all the attributes in the unauthenticated view are referenced in . Notice that some of
the attributes use the term calc for “calculated value.” The value isn't stored as displayed but instead calculated in an
action.
Figure 11.16. All the requests for each view are shown based on the state of the user-agent.
Figure 11.17. This tutorial was created by the user sailsinaction, but it’s being viewed by the user nikolatesla. Therefore,
nikolatesla can rate and view the tutorial but not edit it.
Figure 11.18. A user-agent who is both authenticated and the owner of the tutorial adds an additional request to modify the
tutorial and video. Note that an owner of a tutorial can’t rate their own tutorial.
Figure 11.19. A password-recovery system has several components including a form that captures the email address used when
creating the account, an action that generates a one-time token and the email and responds with an email-sent form , and
the password-reset form with the reset password action .
Chapter 12. Embedded data and associations
Figure 12.1. A model consists of attributes , methods , settings , and an adapter .
Figure 12.2. The tutorials-detail page contains information from the user, tutorial, video, and rating models.
Figure 12.3. Models have relationships that go in different directions, as depicted by the arrows in the figure.
Figure 12.4. The create-tutorial page contains two parameters and two requests.
Figure 12.5. The create-tutorial request reference contains a description , the method and path of the request , backend notes
, incoming parameters , the target action , the response type , and the response .
Figure 12.6. The user model contains a tutorials attribute that will hold the embedded array of tutorial dictionaries as
JSON and an owner attribute that will hold the embedded username dictionary as JSON.
Figure 12.7. The edit-tutorial page contains two parameters and two requests.
Figure 12.8. The edit-tutorial request reference contains a description , the method and path of the request , the backend
notes , incoming parameters , the target action , the response type , and the response .
Figure 12.9. Using async.each() allows you to iterate over the users to find the tutorial before executing User.update().
Figure 12.10. An illustration of embedding a record from one model into the record of another model. The user record’s username
attribute is embedded in the tutorial record’s owner attribute . The entire tutorial record is embedded into the user record’s
tutorials attribute .
Figure 12.11. An illustration of associating a record from one model into the record of another model. The user record’s id
value is now referenced in the tutorial record’s owner attribute . The tutorial record’s id is now referenced in the user
record’s tutorials attribute . Note that the user record’s tutorials attribute won’t be displayed as an array. To obtain what’s
in the tutorials attribute, you’ll use the populate method later in this chapter.
Figure 12.12. Before the title of the tutorial record is changed, if you exchange the tutorials attribute reference for the
actual tutorial record values, you get a title of “The best of Douglas Crockford on JavaScript.” After the title of the tutorial
record is changed , exchanging the tutorials attribute reference for the actual tutorial record values will produce a title
of “The best of Nikola Tesla on Electricity.” Thus, the reference remains in sync after changes.
Figure 12.13. The user model contains a tutorials attribute that configures a collection association between the user and
tutorial models. The tutorial model contains an owner attribute that configures a model association between the tutorial
and user models.
Figure 12.14. Before using .populate(), the user and tutorial records contain references whose value is the id of the associated
record. After using .populate(), those references are populated with the tutorial record and the username attribute of the
user record.
Figure 12.15. The username attribute is displayed, but the tutorials-detail page expects the createdAt and updatedAt attributes
in the time-ago format .
Figure 12.16. The tutorials-detail page with the username , createdAt , and updatedAt properties formatted properly
Chapter 13. Ratings, followers, and search
Figure 13.1. The user model has a ratings association attribute configured as a collection with the rating model that uses
via. The tutorial model has a ratings association attribute configured as a collection with the rating model that uses via.
The rating model has a byUser association attribute configured as a model with the user. The rating model also has a byTutorial
association attribute configured as a model with the tutorial.
Figure 13.2. The tutorials-detail page now displays the correct average rating and a rating for the currently authenticated
user-agent , if any. In this case, the nikolatesla test user has provided a rating of three stars.
Figure 13.3. The tutorial model has a videos association attribute configured as a collection with the video model that uses
via. The video model has a tutorialAssoc association attribute configured as a model with the tutorial model.
Figure 13.4. The create-video page displays attributes from the user, tutorial, and rating models.
Figure 13.5. The frontend expects these attribute formats in the create-video page.
Figure 13.6. The create-video page has some properties you’ll store in the video model, like title and src, and some properties
that you’ll use to calculate the lengthInSeconds on the backend, like hours, minutes, and seconds.
Figure 13.7. Users can reorder the videos using the up and down sort order buttons B.
Figure 13.8. The video player page
Figure 13.9. The Follow Me and Unfollow Me buttons on the user profile toggle whether the user is following another user.
Figure 13.10. The three profile page configurations in authenticated and unauthenticated states
Figure 13.11. The search page has two different requests to the search-result endpoint that uses searchCriteria as an incoming
parameter.
Figure 13.12. The browse-tutorial page contains a browse-results endpoint trigger by the Show More Tutorials button.
Chapter 14. Realtime with WebSockets
Figure 14.1. An HTTP request is necessary for a server to respond. The request creates an open connection between the client
and server. Once the server responds, the connection is closed and another request is needed before the server can again
respond to the client. With WebSockets, once a client makes a connection , the server can send a message to it , at any time
so long as the connection remains open.
Figure 14.2. Each time Sails responds with a server-rendered view, a new WebSocket connection is created.
Figure 14.3. Making a GET request to localhost:1337/example/helloWorld returns the dictionary without the WebSocketId property
and formatted as JSON.
Figure 14.4. Using io.socket.get() to make a request triggers a WebSocket virtual GET request that matches a blueprint action
route and executes the helloWorld action that responds with the WebSocket id via the WebSocketId property.
Figure 14.5. The function signature of the get WebSocket method should look familiar to anyone who has used an AJAX equivalent
method. The function takes the URL as the first argument and the callback as the second argument . Within the callback, you
can get access to the response data as JSON and the JSON WebSocket Response dictionary .
Figure 14.6. Specific to chat, the video player page endpoint will pass seven locals to the view, four of which will be displayed:
gravatarURL and username from the user model, message and created from the chat model. The view also contains a single outgoing
parameter, message, sent within a request, Send chat.
Figure 14.7. A user can have many chats, but a chat can have only one user. A video record can have many chats, but a chat
can have only one video.
Figure 14.8. The model associations between the user, video and chat models. The user model has a chats association attribute
configured as a collection with the chat model that uses via . The video model has a chats association attribute configured
as a collection with the chat model that uses via . The chat model has a sender association attribute configured as a model
with the user . The chat model also has a video association attribute configured as a model with the video .
Figure 14.9. The broadcast method can be executed using the roomName, eventName, and data as arguments . The method can also
be executed without the eventName , in which case the default event name is used. In addition, the currently requesting WebSocket
can be omitted by adding the WebSocketToOmit argument, req. Finally, all four arguments can be used.
Figure 14.10. The significant interactions between the frontend and backend relating to chat
Figure 14.11. The chat event listener displays the chat event message contents.
Figure 14.12. The steps the frontend and backend must fulfill in order to display a message when a user is typing in the message
field of the chat window
Figure 14.13. When two user-agents are on the same video and one begins to type a chat message , the other user-agent has
a typing message displayed .
Chapter 15. Deployment, testing, and security
Figure 15.1. Horizontal scaling of an application on Heroku is accomplished by adding additional dynos to a given project.
Figure 15.2. Heroku’s load-balancing router automatically routes incoming requests to multiple instances of Sails within multiple
dynos.
Figure 15.3. The Brushfire dynos will use a centralized PostgreSQL instance for the main database and a Redis instance for
session and WebSocket storage.
Figure 15.4. You can rename an application directly from the Heroku dashboard under Settings .
Figure 15.5. Brushfire is stored in a local GIT repo on your local machine with two remote repos, one each on Heroku and
GitHub .
Figure 15.6. Heroku offers a variety of add-ons as a service, including PostgreSQL. To obtain a list of all services, click
Find More Add-ons on the dashboard.
Figure 15.7. Locate and click Heroku Postgres from the list of available services. Click Login to Install , and then Install
Heroku Postgres . You’ll want to choose the zero-cost Hobby Dev and click the Provision button. Finally, you’ll see the Heroku
Postgres instance .
Figure 15.8. When Heroku starts the dyno, an environment variable is created in memory . When Sails starts , how do you get
the value of the environment variable into Sails?
Figure 15.9. The value of the DATABASE_URL contains the database credentials including the user , password , host , port ,
and database to the PostgreSQL instance.
Figure 15.10. Heroku allows you to create environment variables, which are set prior to launching Brushfire. When you create
the PostgreSQL instance, the DATABASE_URL config var is also created.
Figure 15.11. The runtime mode dictates which database connection is used at startup.
Figure 15.12. Brushfire deployed and running on Heroku
Figure 15.13. The Mailgun credentials include the API key , the base URL , and the domain .
Figure 15.14. An overview view of the security vulnerabilities of Brushfire’s technical stack includes attacks related to
the frontend, attacks related to the network, and attacks related to the backend.
Figure 15.15. After HTML escaping, the malicious script is rendered harmless before it’s injected into a page.
Figure 15.16. A typical CSRF attack begins with a user logging in to a trusted site. The user clicks a malicious link that
appears to be legitimate but is actually a page on an untrusted site that uses the existing session to do bad things.
Figure 15.17. With CSRF protection enabled, a user logs in to a trusted site. The user clicks a malicious link that appears
to be legitimate but is actually a page on an untrusted site that uses the existing session to attempt to do bad things.
Without the csrf token, a malicious request will be rejected by goodGuys.com.