├── Section2.md ├── Section3.md └── README.md /Section2.md: -------------------------------------------------------------------------------- 1 | # REST API Review - Node/Express 2 | ### Section 2 3 | 4 | 5 | ## Configures knex 6 | 7 | - [ ] First, since we're working with postgres, we'll need to create a database locally. If you haven't already, download [PG Admin](https://www.pgadmin.org/download/) and create a new db for your project. I'm making a drink recipe API later on, so I'll call mine `cocktailRecipeDB`. Call yours something relevant to your application. 8 | 9 | Back in your terminal (in the root folder of this project) 10 | 11 | - [ ] Checkout a new branch called `config/knex` and push it to the remote 12 | - [ ] `npx knex init` 13 | 14 | This creates a new knexfile.js at the root of our project. Edit it to look similar to this: 15 | 16 | ```javascript 17 | // Update with your config settings. 18 | 19 | module.exports = { 20 | 21 | development: { 22 | client: 'pg', 23 | connection: 'postgres://localhost/cocktailRecipeDB', 24 | migrations: { 25 | directory: './data/migrations', 26 | }, 27 | seeds: { 28 | directory: './data/seeds', 29 | }, 30 | pool: { 31 | min: 2, 32 | max: 10, 33 | }, 34 | }, 35 | 36 | production: { 37 | client: 'pg', 38 | connection: process.env.DATABASE_URL, 39 | migrations: { 40 | directory: './data/migrations', 41 | }, 42 | pool: { 43 | min: 2, 44 | max: 10 45 | }, 46 | } 47 | 48 | }; 49 | 50 | ``` 51 | > **NOTE:** I'm using an unsecured Postgres DB here, it should be pretty easy to find the correct config for adding credentials. 52 | 53 | Notice that we don't include a staging environment here. That's because we have two seperate heroku deploys and we want them to mirror each other as closely as possible, so we'll also point our heroku 'Staging' app to the 'Production' knex config. It'll be using a different db anyhow so having the one env in our simple context is ideal. 54 | 55 | - [ ] Commit and push 56 | 57 |
58 | 59 | 60 | ## Creates migration for users table 61 | 62 | In your terminal: 63 | 64 | - [ ] `npx knex migrate:make users` 65 | 66 | - [ ] which will create a `_users.js` file in `data/migrations/`, edit it to look like so: 67 | 68 | __users.js_ 69 | 70 | ```javascript 71 | exports.up = function(knex) { 72 | return knex.schema.createTable('users', users => { 73 | 74 | users.increments(); 75 | 76 | users 77 | .string('username', 128) 78 | .notNullable() 79 | .unique(); 80 | 81 | users 82 | .string('password', 128) 83 | .notNullable(); 84 | 85 | users 86 | .string('email', 128) 87 | .notNullable() 88 | .unique(); 89 | }); 90 | }; 91 | 92 | exports.down = function(knex) { 93 | return knex.schema.dropTableIfExists('users'); 94 | } 95 | ``` 96 | 97 | - [ ] Commit and push this work 98 | 99 | I am not covering seeding in this review. We will use insomnia or postman to populate our database. 100 | 101 | - [ ] **OPTIONAL:** On your own, using google and knexjs.org, find information about seeding a postgres db. Watch out for those primary keys! 102 | 103 | ## Configures db entry point 104 | 105 | - [ ] in the `data/` directory, add a file called `dbConfig.js` and configure knex to process the correct environment. 106 | 107 | _data/dbConfig.js_ 108 | 109 | 110 | ```javascript 111 | const knex = require('knex'); 112 | 113 | const knexConfig = require('../knexfile.js'); 114 | 115 | const environment = process.env.DB_ENV || 'development' 116 | 117 | module.exports = knex(knexConfig[environment]); 118 | ``` 119 | 120 | - [ ] Commit this work and push it up 121 | 122 | ## Adds Users Route 123 | 124 | - [ ] Create a new branch called `feat/users-route` and publish it to the remote 125 | 126 | Now we'll build out the model, router, and basic validation for accessing the `Users` resource, so we can use it when registering or logging in. 127 | 128 | - [ ] At the root of our project, add a `users/` directory, and add to it 3 files: 129 | - [ ] user-helpers.js 130 | - [ ] user-model.js 131 | - [ ] user-router.js 132 | 133 | 134 | - [ ] First we'll write our knex query functions to look something like this: 135 | 136 | _users/user-model.js_ 137 | 138 | ```javascript 139 | const db = require('../data/dbConfig'); 140 | 141 | module.exports = { 142 | add, 143 | find, 144 | findBy, 145 | findById, 146 | update, 147 | remove 148 | }; 149 | 150 | function find() { 151 | return db('users').select('id', 'username', 'email'); 152 | } 153 | 154 | function findBy(filter) { 155 | return db('users').where(filter); 156 | } 157 | 158 | async function add(user) { 159 | const [id] = await db('users').insert(user, 'id'); 160 | return findById(id); 161 | } 162 | 163 | function findById(id) { 164 | return db('users') 165 | .select('id', 'username', 'email') 166 | .where({ id }) 167 | .first(); 168 | } 169 | 170 | function update(id, user) { 171 | return db('users') 172 | .where('id', Number(id)) 173 | .update(user); 174 | } 175 | 176 | function remove(id) { 177 | return db('users') 178 | .where('id', Number(id)) 179 | .del(); 180 | } 181 | ``` 182 | 183 | - [ ] Now in our router, we're only going to write a `GET` to `/`, a `GET` to `/:id`, and `DELETE` to `/:id`. We'll take care of `ADD` in our Register endpoint later. 184 | 185 | _users/user-router.js_ 186 | 187 | ```javascript 188 | const router = require('express').Router(); 189 | 190 | const Users = require('./user-model.js'); 191 | 192 | router.get('/', (req, res) => { 193 | Users.find() 194 | .then(users => { 195 | res.status(200).json(users); 196 | }) 197 | .catch(err => res.send(err)); 198 | }); 199 | 200 | router.get('/:id', (req, res) => { 201 | const id = req.params.id; 202 | if (!id) { 203 | res.status(404).json({ message: "The user with the specified id does not exist." }); 204 | } else { 205 | Users.findById(id) 206 | .then(user => { 207 | res.status(201).json(user) 208 | }) 209 | .catch(err => { 210 | res.status(500).json({ message: 'The user information could not be retrieved.' }); 211 | }) 212 | } 213 | }); 214 | 215 | router.delete('/:id', (req, res) => { 216 | const id = req.params.id; 217 | if (!id) { 218 | res.status(404).json({ message: "The user with the specified ID does not exist." }) 219 | } 220 | Users.remove(id) 221 | .then(user => { 222 | res.json(user); 223 | }) 224 | .catch(err => { 225 | res.status(500).json({ message: 'The user could not be removed' }); 226 | }) 227 | }); 228 | 229 | module.exports = router; 230 | ``` 231 | 232 | - [ ] Let's navigate now into our server and `.use` our new route. 233 | 234 | ```javascript 235 | const express = require('express'); 236 | const cors = require('cors'); 237 | const helmet = require('helmet'); 238 | 239 | const logger = require('../middleware/logger'); 240 | 241 | const usersRouter = require("../users/user-router"); 242 | 243 | const server = express(); 244 | 245 | server.use(helmet()); 246 | server.use(cors()); 247 | server.use(express.json()); 248 | server.use(logger); 249 | 250 | server.use("/api/users", usersRouter); 251 | 252 | server.get('/', (req, res) => { 253 | res.send('

🚀

'); 254 | }) 255 | 256 | module.exports = server; 257 | ``` 258 | 259 | - [ ] Test out your new route by starting up your server and pointing postman or insomnia toward `localhost:5000/api/users`. You should return an empty array with a status of `200`. 260 | 261 | - [ ] Commit this work and push 262 | 263 | 264 | ## Adds validation for `POST` 265 | 266 | Always remember to add good validation for any `POST` or `PUT` methods. The following is an incredibly basic validation function, make sure that when you're validating it's in a more meaningful way. 267 | 268 | - [ ] in your `user-helper.js` 269 | 270 | _users/user-helper.js_ 271 | 272 | ```javascript 273 | module.exports = { 274 | validateUser 275 | }; 276 | 277 | function validateUser(user) { 278 | let errors = []; 279 | 280 | if (!user.username || user.username.length < 2) { 281 | errors.push('Username must contain at least 2 characters'); 282 | } 283 | 284 | if (!user.password || user.password.length < 4) { 285 | errors.push('Password must contain at least 4 characters'); 286 | } 287 | 288 | return { 289 | isSuccessful: errors.length > 0 ? false : true, 290 | errors 291 | }; 292 | 293 | } 294 | ``` 295 | 296 | - [ ] Commit and push 297 | 298 | [Next Page](Section3.md#REST-API-Review--NodeExpress) -------------------------------------------------------------------------------- /Section3.md: -------------------------------------------------------------------------------- 1 | # REST API Review - Node/Express 2 | ### Section 3 3 | 4 | ## Restricts Users Route 5 | 6 | - [ ] Checkout a new branch named `feat/auth` and push it to remote. 7 | 8 | Now we're going to prepare a function to check for and verify a JSON web token. 9 | 10 | - [ ] In the root directory, add another directory named `auth` 11 | - [ ] Add `restricion.js` to your new `auth` directory. 12 | 13 | _auth/restriction.js_ 14 | 15 | ```javascript 16 | const jwt = require('jsonwebtoken'); 17 | 18 | module.exports = (req, res, next) => { 19 | 20 | const token = req.headers.authorization; 21 | 22 | if (token) { 23 | const secret = process.env.JWT_SECRET || "Let me tell you a myth about secrets.."; 24 | 25 | jwt.verify(token, secret, (err, decodedToken) => { 26 | if (err) { 27 | res.status(401).json({ message: 'Invalid Credentials' }); 28 | } else { 29 | req.decodedJwt = decodedToken; 30 | next(); 31 | } 32 | }); 33 | } else { 34 | res.status(400).json({ message: 'No credentials provided' }); 35 | } 36 | 37 | }; 38 | ``` 39 | 40 | - [ ] Then go into your user-router and implement this middleware 41 | 42 | _users/user-router.js_ 43 | 44 | ```javascript 45 | const restricted = require('../auth/restriction.js'); 46 | 47 | router.get('/', restricted, (req, res) => { 48 | Users.find() 49 | ... 50 | ... 51 | } 52 | ``` 53 | 54 | - [ ] Test out this restriction by trying to `GET /api/users` in Insomnia or Postman and looking for the relevant error. 55 | 56 | - [ ] Commit and push 57 | 58 | ## Adds tests for Login and Register endpoints 59 | 60 | - [ ] Write meaningful tests for both Login and Register endpoints that take into account any constraints of your DB (again, since we're using Postgres here watch out for those primary keys!) 61 | 62 | ## Adds Login and Register endpoints 63 | 64 | - [ ] In `auth/` create 3 files 65 | - [ ] auth-helpers.js 66 | - [ ] login-router.js 67 | - [ ] register-router.js 68 | 69 | 70 | - [ ] First write a helper function to get the JSON Web Token 71 | 72 | _auth/auth-helpers.js_ 73 | 74 | ```javascript 75 | const jwt = require('jsonwebtoken'); 76 | 77 | module.exports = { 78 | getJwt 79 | } 80 | 81 | function getJwt(username) { 82 | const payload = { 83 | username 84 | }; 85 | 86 | const secret = process.env.JWT_SECRET || "Let me tell you a myth about secrets.."; 87 | 88 | const options = { 89 | expiresIn: '1d' 90 | }; 91 | 92 | return jwt.sign(payload, secret, options) 93 | } 94 | ``` 95 | 96 | - [ ] Then write a route for logging in, using our new `getJWT()` function 97 | 98 | _auth/login-router.js_ 99 | 100 | ```javascript 101 | const router = require('express').Router(); 102 | const bcrypt = require('bcryptjs'); 103 | const jwt = require('jsonwebtoken'); 104 | 105 | const Users = require('../users/user-model'); 106 | const Token = require('./auth-helpers.js'); 107 | 108 | router.post('/', (req, res) => { 109 | 110 | let { username, password } = req.body; 111 | 112 | Users.findBy({ username }) 113 | .first() 114 | .then(user => { 115 | 116 | if (user && bcrypt.compareSync(password, user.password)) { 117 | 118 | const token = Token.getJwt(user.username); 119 | 120 | res.status(200).json({ 121 | id: user.id, 122 | username: user.username, 123 | token 124 | }); 125 | } else { 126 | res.status(401).json({ message: 'Invalid credentials' }); 127 | } 128 | }) 129 | .catch(error => { 130 | res.status(500).json(error); 131 | }); 132 | }) 133 | 134 | 135 | module.exports = router; 136 | ``` 137 | 138 | - [ ] And a route for registering, using the same `getJWT()` function. 139 | 140 | _auth/register-router.js_ 141 | 142 | ```javascript 143 | const router = require('express').Router(); 144 | const bcrypt = require('bcryptjs'); 145 | 146 | const Users = require('../users/user-model.js'); 147 | const Token = require('./auth-helpers.js'); 148 | const { validateUser } = require('../users/user-helpers.js'); 149 | 150 | router.post('/', (req, res) => { 151 | 152 | let user = req.body; 153 | 154 | const validateResult = validateUser(user); 155 | 156 | if (validateResult.isSuccessful === true) { 157 | 158 | const hash = bcrypt.hashSync(user.password, 10); 159 | user.password = hash; 160 | 161 | const token = Token.getJwt(user.username); 162 | 163 | Users.add(user) 164 | .then(saved => { 165 | res.status(201).json({ id: saved.id, username: saved.username, token: token }); 166 | }) 167 | .catch(error => { 168 | res.status(500).json(error); 169 | }) 170 | 171 | } else { 172 | 173 | res.status(400).json({ 174 | message: 'Invalid user info, see errors', 175 | errors: validateResult.errors 176 | }); 177 | } 178 | }) 179 | 180 | module.exports = router; 181 | ``` 182 | 183 | - [ ] Don't forget to incorporate your validation function from before into each `post`, both login and register routers need this validation. 184 | 185 | - [ ] Now go into your server and `.use` these new routes 186 | 187 | ```javascript 188 | const express = require('express'); 189 | const cors = require('cors'); 190 | const helmet = require('helmet'); 191 | 192 | const logger = require('../middleware/logger'); 193 | 194 | const usersRouter = require("../users/user-router"); 195 | const loginRouter = require("../auth/login-router.js"); 196 | const registerRouter = require("../auth/register-router.js"); 197 | 198 | const server = express(); 199 | 200 | server.use(helmet()); 201 | server.use(logger); 202 | server.use(express.json()); 203 | server.use(cors()); 204 | 205 | server.use("/api/login", loginRouter); 206 | server.use("/api/register", registerRouter); 207 | server.use("/api/users", usersRouter); 208 | 209 | server.get('/', (req, res) => { 210 | res.send('

🎣

'); 211 | }) 212 | 213 | module.exports = server; 214 | ``` 215 | 216 | - [ ] Commit and push these changes 217 | 218 | ## Updates documentation 219 | 220 | - [ ] On a new branch, make sure that each of your `/api/...` endpoints are documented well, including any body data they require, filtering they may offer, validation included, and what data each endpoint returns. Document this clearly and concisely, so that it is very easy to browse. 221 | 222 | - [ ] Commit and push these changes 223 | 224 | ## Deploys 225 | 226 | We're ready to merge these new changes into main now, so let's first go to Heroku and get our staging application ready. 227 | 228 | - [ ] First, navigate to the `Resources` tab. 229 | - [ ] Under `Add-ons` search for postgres and provision a new DB. 230 | 231 | ![Provision new Postgres DB in Heroku][postgres] 232 | 233 | [postgres]: https://res.cloudinary.com/thisbenrogers/image/upload/v1582772590/Screen_Shot_2020-02-26_at_9.00.20_PM_fi26bu.png 234 | 235 | Once the Postgres DB is added, you'll be able to navigate to `Settings` in your Heroku staging application, and click on `Reveal Config Vars`. Here you'll notice that Heroku has added a `DATABASE_URL` for us. Cool! We'll need to add a few more Config Vars too while we're here. 236 | 237 | ![Heroku Config Vars][config] 238 | 239 | [config]: https://res.cloudinary.com/thisbenrogers/image/upload/v1582772860/Screen_Shot_2020-02-26_at_9.07.00_PM_q1wp2g.png 240 | 241 | - [ ] Add a `DB_ENV` key with a value of `production` 242 | - [ ] Add a `JWT_SECRET` key with a secure value of your choosing. 243 | 244 | - [ ] Now, merge your most recent changes into `main`, and check your Heroku Activity feed for progress/errors 245 | - [ ] Once the application is deployed, we'll need to run our migrations. There are a few ways to do this, but for simplicity's sake here we'll use Heroku's Console that they provide under the `More` dropdown in the top right of your application dashboard. Choose `Run Console` 246 | 247 | ![Access Heroku Console][console] 248 | 249 | [console]: https://res.cloudinary.com/thisbenrogers/image/upload/v1582772590/Screen_Shot_2020-02-26_at_9.01.18_PM_m84d1h.png 250 | 251 | - [ ] run `knex migrate:latest`, and once the batch has run, 252 | - [ ] Using Postman or Insomnia, register a new user at the URL of your deployed app (`/api/register`) 253 | - [ ] Using Postman or Insomnia, try to access `/api/users` with the token that is returned from `register` as an `authorization` header 254 | - [ ] Once this staging application is running as expected, go into your `production` application and: 255 | - [ ] Provision a Postgres DB 256 | - [ ] Add `DB_ENV` and `JWT_SECRET` Config Vars 257 | - [ ] Manually deploy from `main` 258 | - [ ] Run your migrations 259 | - [ ] Test out production -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REST API Review - Node/Express 2 | ## Knex, PostgreSQL, JWT, deployed on Heroku 3 | ### With an emphasis on git hygiene and deployment practices 4 | 5 | ## Table of Contents 6 | - [Page 1](README.md) Deploy Simple Express App 7 | - [Description](#Description) 8 | - [Initial Commit](#Initial-Commit) 9 | - [Connect To Remote](#Connect-To-Remote) 10 | - [Initializes Node/NPM](#Initializes-NodeNPM) 11 | - [Installs Dependencies](#Installs-Dependencies) 12 | - [Updates package.json](#Updates-package.json) 13 | - [Writes Test for Server Endpoint](#Writes-Test-for-Server-Endpoint) 14 | - [Adds Server Endpoint](#Adds-Server-Endpoint) 15 | - [Adds Logger Middleware](#Adds-Logger-Middleware) 16 | - [Deploys Application to Heroku Staging](#Deploys-Application-to-Heroku-Staging) 17 | - [Updates Documentation](#Updates-Documentation) 18 | - [Deploys application to Heroku production](#Deploys-application-to-Heroku-production) 19 | - [Page 2](Section2.md) Build User Routes 20 | - [Configures knex](Section2.md#Configures-knex) 21 | - [Creates migration for users table](Section2.md#Creates-migration-for-users-table) 22 | - [Configures db entry point](Section2.md#Configures-db-entry-point) 23 | - [Adds Users Route](Section2.md#Adds-Users-Route) 24 | - [Adds validation for `POST`](Section2.md#Adds-validation-for-POST) 25 | - [Page 3](Section3.md) Deploy Auth 26 | - [Restricts Users Route](Section3.md#Restricts-Users-Route) 27 | - [Adds tests for Login and Register endpoints](Section3.md#Adds-tests-for-Login-and-Register-endpoints) 28 | - [Adds Login and Register endpoints](Section3.md#Adds-Login-and-Register-endpoints) 29 | - [Updates documentation](Section3.md#Updates-documentation) 30 | - [Deploys](Section3.md#Deploys) 31 | 32 | 33 |
34 | 35 | **requirement:** you must have [pgadmin](https://www.pgadmin.org/download/) installed for this review. 36 | 37 |
38 | 39 | ## Description 40 | 41 | This document is meant to serve as a checklist review of materials taught in the Node Unit of the Full Stack Web Curriculum at Lambda School. 42 | 43 | Upon completion of this review, you should have a simple application that _could_ be used as a boilerplate, upon which you can build out custom models, routes, and helpers to suit individual needs. However, don't use a boilerplate to do something like this until you can build it from scratch on your own. Do the reps to earn it, this document is just a guide. 44 | 45 | The code samples included in this review should be considered a 'bare minimum' and should be embellished on in any way. In some instances there is no code included, simply instructions to author something on your own. Seriously don't just copy this. Write excellent tests, thoughtful documentation, and return meaningful data. Some commands and code examples may differ slightly from what you were taught in lectures, but the underlying principles will remain the same. 46 | 47 | 48 | ## Initial Commit 49 | 50 | Create a directory for your API to live in 51 | 52 | In your terminal: 53 | 54 | - [ ] `mkdir < server-directory-name > && cd < server-directory-name >` 55 | 56 | > **NOTE:** the `&&` in this first command executes both commands in sequence, or one after the other. Having this command on one line creates the directory and then changes into it so that the following commands are executed in the newly created root directory. Great for this use case, but use with caution in other contexts. 57 | > 58 | > All further terminal commands in this review take place in the root directory. 59 | 60 | - [ ] `git init` 61 | - [ ] `npx gitignore node` //creates .gitignore for node 62 | 63 |
64 | 65 | ## Connect to remote 66 | 67 | 68 | - [ ] Login to your github account and create a new repo. Don't add a README or a .gitignore, we're doing that ourselves. Give the repo a meaningful name (I like to use the same name as the local directory we created above), and once it's created copy the URL provided. 69 | 70 | In your terminal: 71 | 72 | - [ ] `git add .gitignore` 73 | - [ ] `git commit -m "Initial Commit"` 74 | - [ ] `git remote add origin ` (the url from your github remote) 75 | - [ ] `git push -u origin main` 76 | - [ ] `git checkout -b "initialize"` 77 | - [ ] `git push -u origin initialize` 78 | 79 | Now you have a remote repo connected to your local repo, and you're currently developing on a newly published branch called _initialize_. 80 | 81 | 82 |
83 | 84 | 85 | ## Initializes Node/NPM 86 | 87 | In your terminal: 88 | 89 | - [ ] `npm init -y` //creates package.json 90 | - [ ] `git add package.json` 91 | - [ ] `git commit -m "Initializes Node/NPM"` 92 | 93 | > **NOTE:** The `-y` flag answers 'yes' to all `npm init` configuration questions. We don't need anything more configured than the stock package.json for now, but explore `npm init` without the `-y` flag later so you have a better idea of what's going on here. 94 | 95 |
96 | 97 | ## Installs dependencies 98 | 99 | In your terminal: 100 | 101 | - [ ] `npm i express helmet cors knex knex-cleaner dotenv pg bcryptjs jsonwebtoken` 102 | - [ ] `npm i nodemon cross-env jest supertest -D` 103 | - [ ] `git add package.json package-lock.json` 104 | - [ ] `git commit -m "Installs dependencies"` 105 | 106 |
107 | 108 | 109 | ## Updates package.json 110 | 111 | - [ ] Add server (and start) script(s): 112 | 113 |
114 | 115 | _package.json_ 116 | 117 | ```json 118 | "scripts": { 119 | "server": "nodemon", 120 | "start": "node index.js" 121 | } 122 | ``` 123 |
124 | 125 | The `start` script here is used by Heroku. The value we give it tells Heroku to run `node index.js` to start our server. 126 | 127 | In your terminal: 128 | 129 | - [ ] `git add package.json` 130 | - [ ] `git commit -m "Updates package.json"` 131 | - [ ] `git push` 132 | 133 | > **NOTE:** You may notice that the `-m` message in each of these commits so far is taken from the header of that section. Throughout this review, continue to follow the commit format laid out above, and if you need to see which files to `add` use `git status`. Following this commit flow will yield a really nice commit history that can also serve as a checklist on future reps through this development process. GIT HYGIENE IS SO IMPORTANT TO _GOOD_ EMPLOYERS!!!! 134 | 135 | 136 |
137 | 138 | ## Writes test for Server endpoint 139 | 140 | Start a new branch called `tests/server-endpoint`: 141 | 142 | - [ ] `git checkout -b "tests/server-endpoint"` 143 | - [ ] `git push -u origin tests/server-endpoint` 144 | 145 | Add the following files to the root directory, and don't forget the `API` directory: 146 | 147 | - [ ] index.js 148 | - [ ] API/server.js 149 | - [ ] API/server.spec.js 150 | 151 | - [ ] build your application's entry point in index.js 152 | 153 | _index.js_ 154 | 155 | ```javascript 156 | const server = require('./API/server.js'); 157 | 158 | const port = process.env.PORT || 5000; 159 | server.listen(port, () => { 160 | console.log(`\n<<< Server running on port ${port} >>>\n`); 161 | }) 162 | ``` 163 | 164 | - [ ] and add a simple test: 165 | 166 | _API/server.spec.js_ 167 | 168 | ```javascript 169 | const request = require("supertest"); 170 | 171 | const server = require("./server.js"); 172 | 173 | it("should set db environment to testing", function() { 174 | expect(process.env.DB_ENV).toBe("testing"); 175 | }); 176 | 177 | describe("server", function() { 178 | describe("GET /", function() { 179 | it("should return 200", function() { 180 | // run the server 181 | // make a GET request to / 182 | // see that the http code of response is 200 183 | return request(server) 184 | .get("/") 185 | .then(res => { 186 | expect(res.status).toBe(200); 187 | }); 188 | }); 189 | 190 | it("should return HTML", function() { 191 | return request(server) 192 | .get("/") 193 | .then(res => { 194 | expect(res.type).toMatch(/html/i); 195 | }); 196 | }); 197 | }); 198 | }); 199 | ``` 200 | 201 | - [ ] then add a test script to our package.json: 202 | 203 | _package.json_ 204 | 205 | ```json 206 | "scripts": { 207 | "server": "nodemon", 208 | "start": "node index.js", 209 | "test": "cross-env DB_ENV=testing jest --watch" 210 | }, 211 | ``` 212 | 213 | - [ ] And add a very simple express server: 214 | 215 | _API/server.js_ 216 | 217 | ```javascript 218 | const express = require('express'); 219 | 220 | const server = express(); 221 | 222 | module.exports = server; 223 | ``` 224 | 225 | Then run our tests to watch them fail first, 226 | 227 | in your terminal: 228 | 229 | - [ ] `npm run test` 230 | 231 | - [ ] Now commit this work and push it to the remote. 232 | 233 | 234 |
235 | 236 | ## Adds server endpoint 237 | 238 | - [ ] Create a new branch called `feat/server-endpoint` and push it up to the remote. 239 | 240 | - [ ] Let's finish building out our basic server endpoint so that test can pass: 241 | 242 | _API/server.js_ 243 | 244 | ```javascript 245 | const express = require('express'); 246 | const cors = require('cors'); 247 | const helmet = require('helmet'); 248 | 249 | const server = express(); 250 | 251 | server.use(helmet()); 252 | server.use(cors()); 253 | server.use(express.json()); 254 | 255 | server.get('/', (req, res) => { 256 | res.send('

🚀

'); 257 | }) 258 | 259 | module.exports = server; 260 | ``` 261 | 262 | Then, in your terminal: 263 | 264 | - [ ] `npm run server`, and once you see the server is running in your console, 265 | - [ ] `npm run test` (in a new terminal window, or stop the server first) 266 | 267 | - [ ] Commit this work, as long as your test passes. 268 | 269 |
270 | 271 | ## Adds Logger middleware 272 | 273 | Adding a logger middleware early is a good idea, so let's do that now. 274 | 275 | - [ ] Make a new branch called `middleware/logger` and push it up to the remote. 276 | 277 | - [ ] In your root directory, add a new directory called `middleware` 278 | - [ ] Create a file called `logger.js` inside of `middleware` 279 | 280 | _middleware/logger.js_ 281 | 282 | ```javascript 283 | module.exports = logger; 284 | 285 | function logger(req, res, next) { 286 | console.log(`${req.method} to ${req.url} at ${new Date().toISOString()}`) 287 | next(); 288 | }; 289 | ``` 290 | 291 | - [ ] and then let's `.use` the logger in our server.js 292 | 293 | _API/server.js_ 294 | 295 | ```javascript 296 | const express = require('express'); 297 | const cors = require('cors'); 298 | const helmet = require('helmet'); 299 | 300 | const logger = require('../middleware/logger'); 301 | 302 | const server = express(); 303 | 304 | server.use(helmet()); 305 | server.use(cors()); 306 | server.use(express.json()); 307 | server.use(logger); 308 | 309 | server.get('/', (req, res) => { 310 | res.send('

🚀

'); 311 | }) 312 | 313 | module.exports = server; 314 | ``` 315 | 316 | - [ ] Run your server and point your browser to `localhost:5000`. You should see the rocket emoji in your browser window, and when you come back to your terminal you should see something like: `GET to / at dateTime`, which means your logger is logging. 317 | 318 | - [ ] Commit this work and push it up. 319 | 320 |
321 | 322 | ## Deploys application to Heroku staging 323 | 324 | We now have a super simple express server running, so it's time to deploy! Try to deploy as soon as you have _something_ working, so that whenever you're ironing out any issues in staging, you'll only have to troubleshoot the smallest codebase possible. Deploy early, commit often! 325 | 326 |
327 | 328 | --- 329 | 330 | To explain the 3 different environments we'll be using: 331 | 332 | - **Development** is the environment on our local machines 333 | - This is where we write the code 334 | - **Staging** is (in this context) the environment on Heroku that _continuously_ deploys anything on `main` 335 | - This is where we test and troubleshoot any deploy issues 336 | - **Production** is (in this context) the environment on Heroku that we _manually_ deploy from `main` once staging looks good 337 | - This is where any client that needs our API accesses it (like a React app) 338 | - We _only_ manually deploy this app once the new features on our deployed staging server are working as we expect, so that we can be confident in our production server's stability. 339 | 340 | --- 341 |
342 | 343 | - [ ] [Signup/login](https://dashboard.heroku.com/login) to a free Heroku account. 344 | 345 | - [ ] Once logged into your Heroku dashboard, click on `New` in the top Right corner, and then on `Create new app` 346 | 347 | ![Create New Heroku App][new] 348 | 349 | [new]: https://res.cloudinary.com/thisbenrogers/image/upload/v1582772590/Screen_Shot_2020-02-26_at_8.38.42_PM_ruzjml.png 350 | 351 | - [ ] Give your app a meaningful name, and be sure to use the word `staging` to differentiate it from the `production` app we'll build in a bit. Then click on `Create app` 352 | 353 | - [ ] Once your app is created, navigate to the `Deploy` tab (Heroku tends to drop you off here anyhow) and select `GitHub` as the Deployment Method. 354 | 355 | - [ ] Connect the app to your remote GitHub respoitory, and enable automatic deploys on `main` branch. 356 | 357 | ![Connect Heroku to Github Remote][github] 358 | 359 | [github]: https://res.cloudinary.com/thisbenrogers/image/upload/v1582772590/Screen_Shot_2020-02-26_at_8.42.27_PM_ressp4.png 360 | 361 | Now, since our `main` branch currently only contains a .gitignore file, we'll need to merge our latest `middleware/logger` branch into `main` so Heroku will have something to deploy. 362 | 363 |
364 | 365 | In your terminal: 366 | 367 | - [ ] `git checkout main` 368 | 369 | since we know there are no changes on the remote that we need to pull into our branch first, we can: 370 | 371 | - [ ] `git merge middleware/logger` 372 | - [ ] `git push origin main` 373 | 374 |
375 | 376 | > If there were remote changes that we needed to pull into our local branch first, we would need to instead run: 377 | > 378 | > - `git checkout main` 379 | > - `git pull --rebase origin main` 380 | > - `git checkout middleware/logger` 381 | > - `git pull --rebase origin main` 382 | > - `git checkout main` 383 | > - `git merge middleware/logger` 384 | > - `git push origin main` 385 | 386 |
387 | 388 | Now in your Heroku Dashboard, you can click on the `Activity` tab and watch Heroku build the application. 389 | 390 | ![Heroku Activiy Tab][activity] 391 | 392 | [activity]: https://res.cloudinary.com/thisbenrogers/image/upload/v1582772590/Screen_Shot_2020-02-26_at_8.46.17_PM_k4hbnn.png 393 | 394 | - [ ] Once you see a `Build Succeeded` and a `Deployed` message in the `Activity ` tab, click on 'Open app' in the top right corner and hope for a rocket! 395 | 396 | - [ ] While you're in your Heroku dashboard, go ahead and create a new app for production following the above guidance. Connect it to the exact same repository's `main` branch just **DON'T deploy it yet!** 397 | 398 |
399 | 400 | 401 | ## Updates Documentation 402 | 403 | Now that we have a staging app deployed, it's time to create and update our README.md to reflect the changes we're about to take live on our production deployment. You'll want to follow this deployment flow to allow for the smoothest introduction of new features for anyone working on a client that would be consuming this API: 404 | > deploy to staging > test/troubleshoot staging deploy > update docs > deploy to production 405 | 406 | If you follow this flow every time you're deploying new features, you'll save yourself the rush to explain everything you've changed. All the new features will be documented in the `main` README before the `production` deploy completes, so your team will remain informed about the current state of the deploy. 407 | 408 | Use the [Standard Readme](https://github.com/RichardLitt/standard-readme) spec for guidance, and include plenty of meaningful info in your README like a link to the deployed production API (if you already created your production deploy in Heroku, the URL for the production app will be `https://NAME-OF-PRODUCTION-APP-HERE.herokuapp.com`) 409 | 410 | - [ ] Create and edit the README.md on a new branch named `docs/README` and push it up to the remote. Then merge that branch into `main` 411 | 412 |
413 | 414 | ## Deploys application to Heroku production 415 | 416 | Now that we've updated our docs and been through any troubleshooting on the staging app, we can deploy to production. 417 | 418 | - [ ] In your Heroku Dashboard, navigate to the Production app (**not** staging) that we created earlier, and go to the `Deploy` tab. Make sure that automatic deploys are turned off, then manually deploy the `main` branch. 419 | 420 | Your staging app is continuously deploying from `main` because we want our newest edits to be live for us to test asap, but we manually deploy production so that we can be sure everything is working correctly before we take our new edits live. 421 | 422 | - [ ] Once your production app is deployed, test it out like you did for staging (hopefully rocket!) and pat yourself on the back. 423 | 424 | Next we'll create our database and add routes for users to register and login. 425 | 426 |
427 | 428 | [Next Page](Section2.md#REST-API-Review--NodeExpress) --------------------------------------------------------------------------------