Express, like Rails, can be used as an API. In fact, building APIs in Express, especially those that use MongoDB for persistence, led to the rising popularity of Node.
Express can be used for full-stack applications (those that have server-rendered views). However, we will use it purely as an API.
A customized template for Express is available at ga-wdi-boston/express-template. It includes authentication and common middlewares so that you can start developing an API right away.
By the end of this, developers should be able to:
- Develop an Express API, leveraging architectural conventions from Rails.
- Write five CRUD endpoints for an API resource using Express, Mongoose, and JavaScript.
- Prevent unauthorized users from creating or changing data through the API.
- Fork and clone this repository.
- Install dependencies with
npm install
. - Verify monogdb is runnning with
brew services list
(Runbrew services restart mongodb
if not) - Set a SECRET_KEY in the environment.
- Run the API server with
npm start
. If you want your code to be reloaded on change, you shouldnpm install -g nodemon
and usenodemon
instead ofnpm start
.
For development and testing, set the SECRET_KEY from the root of your repository using:
echo SECRET_KEY=$(/usr/local/opt/openssl/bin/openssl rand -base64 66 | tr -d '\n') >>.env
We've been hired to write an API for a local bookstore, 'Book Before You Leap'. They have plans to expand in the next few years, and they'll probably rival Amazon. Therefore, we've chosen Express because it's hip, and Mongo because it's Web Scale™.
Let's get acquainted with how we'll use Express.
First, let's peek at our routes, since that's the layer that decides which code
to run for any given request. Open config/routes.js
and
read through it. Look familiar?
Have a look in the app
directory. It looks a bit like Rails, too.
In app/controllers/examples.js
, we get our
first taste of Express. What's the (req, res, next)
signature on all our
controller actions?
The req
object is a
http.IncomingMessage
object. The res
object is
http.ServerResponse
object. These are what we used in the node HTTP server. What about next
?
More than one callback function can handle a route (make sure you specify the next object).
That means more than one action can be run for a single request. In fact,
that's how Express keeps boilerplate to a minimum; we did something similar with
before_filter
s in Rails. Common functionality, like error handling, can be
extracted into a middleware and run on any request you like. However, you
must use next
to propagate errors onward.
Likewise, res.json
signals to Express that we're done working on our response.
It's analogous to Rails' render
method. If you don't use a terminal
handler, Express will keep the connection open waiting for one. You and
Express will both be frustrated and confused. Here's a list of terminal
handlers. You will use res.json
and res.sendStatus
most frequently.
Response method | What it means |
---|---|
res.end() |
End the response process. |
res.json(jsObject) |
Send a JSON response. |
res.redirect() |
Redirect a request. |
res.sendStatus() |
Set the response status code and send its string representation as the response body. |
Practice reading unfamiliar code by annotating
app/controllers/examples.js
. As you read each
controller action, keep the following questions in mind. You will be asked to
answer them when we share our notes.
- What is the purpose of this action?
- Does it need a singular or plural resource to build its response?
- How is the action handling errors?
- Why do we need to check for the existence of a record after querying?
- Where do we get IDs from?
- Where do we get data from when creating or updating a record?
- Which terminal handler is used to send a response?
Let's read app/models/example.js
and answer the
following questions together:
- What library are we using to model our resources? Does it have anything to do with Express?
- What does the underscore denote in
_owner
? - Where should we go to find out more about an owner?
- Why aren't we using an arrow function for the virtual attribute
length
?
Start the server and try creating an example by issuing a POST /examples
. What
happens? You might find some help in the scripts
directory.
When you've created an example, try using its ID to request it via GET /examples/:id
. Then, create another and check your work with GET /examples
.
Only authenticated users should be able to create a book.
Let's create our book model together, and one controller action. Don't forget a route!
Make sure to save a reference to the user that created the book so we can user it later to check ownership.
We'll need to write a test script to check our work. We'll save it as
scripts/books-create.sh
.
We're done when we see a response similar to this one:
Expected response:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"book": {
"__v": 0,
"updatedAt": "2016-03-09T03:23:58.000Z",
"createdAt": "2016-03-09T03:23:58.000Z",
"_owner": "56df9716c19957cb0d836c4a",
"title": "Invisible Monsters",
"author": "Chuck Palahniuk",
"price": 10.99,
"_id": "56df974ec19957cb0d836c4d"
}
}
Visitors to the client web application should be able to see all the books without being logged in.
We will need to write a controller action and a test script.
Expected response:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"books": [
{
"_id": "56df974ec19957cb0d836c4c",
"updatedAt": "2016-03-09T03:23:58.000Z",
"createdAt": "2016-03-09T03:23:58.000Z",
"_owner": "56df9716c19957cb0d836c4a",
"title": "Between the World and Me",
"author": "Ta-Nehisi Coates",
"price": 12.99,
"__v": 0
},
{
"_id": "56df974ec19957cb0d836c4d",
"updatedAt": "2016-03-09T03:23:58.000Z",
"createdAt": "2016-03-09T03:23:58.000Z",
"_owner": "56df9716c19957cb0d836c4a",
"title": "Invisible Monsters",
"author": "Chuck Palahniuk",
"price": 10.99,
"__v": 0
}
]
}
Visitors to the client web application should be able to see any book without being logged in.
You will need to write a controller action and a test script.
Expected response:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"book": {
"_id": "56df974ec19957cb0d836c4c",
"updatedAt": "2016-03-09T03:23:58.000Z",
"createdAt": "2016-03-09T03:23:58.000Z",
"_owner": "56df9716c19957cb0d836c4a",
"title": "Between the World and Me",
"author": "Ta-Nehisi Coates",
"price": 12.99,
"__v": 0
}
}
Only authenticated users should be able to change a book. They should not be able to change other users' books.
You will need to write a controller action and a test script.
Expected response:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
You may wish to retrieve the book you changed to check your work.
If a different user than the owner tries to make the change, you should instead see:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"error": {
"message": "Not Found",
"error": {
"status": 404
}
}
}
Only authenticated users should be able to delete a book. They should not be able to delete other users' books.
You will need to write a controller action and a test script.
Expected response:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
If a different user than the owner tries to make the change, you should instead see:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
{
"error": {
"message": "Not Found",
"error": {
"status": 404
}
}
}
- Express - Node.js web application framework
- ga-wdi-boston/express-template: Railsified express server
Source code distributed under the MIT license. Text and other assets copyright General Assembly, Inc., all rights reserved.