This section will teach you the steps you need to setup a project and review
some node
basics that you may already know. Make sure you are in a terminal.
If in any steps you are stuck because of a running program in the terminal,
try pressing Control c
or Control d
or Control z
to terminate the
process.
mkdir project1
cd project1
npm init
Control c
to quit and type
npm init -y
to autofill the questions asked.package.json
.If you are unfamiliar with the 3 steps above, it may help to delete the folder and then repeat the steps until you are completely comfortable creating a new project.
Package.json contains information about your project. This is a big JSON string (not a javascript object) so you must make sure you pay attention to your format if you modify the file.
We will only go over the important aspects properties of this file. Feel free to play and explore, if you mess up you can delete the project folder and create a new one.
To start, create a file (in this example, we will use index.js
). Open the file
and run a few console.log
statements:
console.log('hello')console.log('world')
Run your file to make sure it works: node index.js
scripts
section allows you to define commands. By default, you should have a
test
property. Let's create our own script called start
that runs our file:
Since this is a JSON string, make sure you are always using double quotes and don't put comma for the last property in the object.
After saving the file, you should be able to run your file by typing
npm run start
to execute the command. When your project gets bigger, it can
get pretty complicated to run your files, so writing scripts makes it easy for
developers working on the project. You can write as many scripts as you want.
Here is an example of the scripts
in C0D3's codebase.
If you want to add a library (moment
for example) to your project, type
npm i --save moment
You will notice 3 things when you do that:
node_modules
will be created if it did not already exist.
Within that folder, you will see a folder called moment
that contains all
the files for moment. You may notice other folders, these folders contains
the libraries that moment
may need to run.package.json
file will have a dependencies
section containing
moment
. This section will contain all the libraries that your project
depend on.
Here is an example of the libraries
that C0D3 depends on.package-lock.json
file will be created. This file not only contains the
libraries that your project depend on, it also contains all the dependent
libraries that the libraries themselves depend on. It also lists the specific
version of each library. This way, everyone who works on your project will
have exactly the same libraries.When you work on other people's projects, first download the code onto your
computer. Then run npm i
to install all the libraries (specified in
package-lock.json
) that the applications need. All the libraries will be
installed into node_modules
folder.
At this point, you should have moment
library installed and ready to use.
const moment = require('moment')
into your index.js
file.// index.jsconst moment = require('moment')console.log('hello')console.log('world')const a = moment().startOf('day').fromNow()const b = moment().startOf('week').fromNow()console.log('a is ', a)console.log('b is ', b)
In the previous todo list exercise, you sent requests to our API to create user session, create user accounts, and manage todo lists. In this section, you will learn how to build your own server so you can accept requests and send back responses.
First, create a project and add a library called express
. There are many
libraries that can help us accept requests and express
is the most popular.
Add the following code
// index.jsconst express = require('express') // Import the express libraryconst app = express() // Use the express library to create an appapp.listen(3000) // Your app needs to listen to a port number.
app.listen
keeps your application running so you can listen to incoming
requests. If your application stops running, then nothing will be listening to
incoming requests. Your server is down when the application stops running. To
shut down your server type CTRL c
In order to listen to incoming requests, you need to provide a port number. You can make this number up and as long as no other applications (like games, email, messages, etc) running in your computer is already listening on the same port. Usually it is safe to pick a number 3000 and higher.
Only one application may listen to a certain port at any given time.
Using the app
object, you can also listen for specific request method and
path.
The image below says the request handler function will be called with 2 arguments, a request and a response object, for simplicity. There is actually a third argument which is a function. This third argument will be covered in the section below about middleware.
Request Handler - The function that runs when a request comes into the function.
Putting it together, let's run your first server!
// index.jsconst express = require('express') // Import the express libraryconst app = express() // Use the express library to create an app// Whenever a request comes in at the path `/hello`, you send back some HTML!app.get('/hello', (req, res) => {res.send('<h1> Hello world </h1>')})app.listen(3000) // Your app needs to listen to a port number.
To send a request, open your browser. In the url, type in: https://pathacks.com:3000/hello
Remember to restart your application each time you make changes to your code so that your change is reflected.
For now, the only request methods you can experiment with are requests without a
body: GET
and DELETE
.
Sample code to send back 'ok' when we receive a DELETE
request at the path
'/hello', and sends back HTML when we receive GET
request at the path
'/hello'.
// index.jsconst express = require('express') // Import the express libraryconst app = express() // Use the express library to create an appapp.get('/hello', (req, res) => {res.send('<h1> Hello world </h1>')})app.delete('/hello', (req, res) => {res.send('ok')})app.listen(3000) // Your app needs to listen to a port number.
The first argument specifies a url path string. It also accepts other data types and the most common url patterns are:
/hello
- Request handler will run specifically when the request path matches
/hello
['/hello', '/world']
- Request handler will run when the request path
matches either /hello
or /world
/*
- Request handler will run for all pathsTo create a request handler for all request methods, you can use app.use
>
app.use(path, requestHandler)
The answers use a different variable name. Instead of app
, you could have used
any variable.
Create a get path called /count
that
will tell users how many times that path has been visited.
let visitorCount = 0router.get('/count', async (req, res) => {visitorCount = visitorCount + 1return res.send(`<h1>Welcome, this page has been visited ${visitorCount} times</h1>`)})
Create a get path called /delay
that
will send back a response only after 5 seconds (We are simulating a really
slow response).
router.get('/delay', async (req, res) => {setTimeout(() => {return res.send(`<h1>Your request has been delayed by 5 seconds</h1>`)}, 5000)})
GetFile: Create a file with the file
name hello
. Create a get path called /getfile
that sends back the file.
Hint: you need to use node's fs
and then fs.readFile
to read the contents
of the file.
router.get('/getfile', async (req, res) => {fs.readFile('./hello', (err, data) => {if (err) return res.status(500).send('Error: Error reading file')res.send(`<div>${data}</div>`)})})
If you don't enclose data
variable inside an HTML string, then res.send
will send back the literal file contents, which serves a different purpose. A
better way to send actual files is to simply call res.sendFile
.
Easy AB Testing. Create a path called
/abtest
. It will display "Hello World" in green 1 in 5 times. The rest of
the times, it will display in red.
let visitorCount2 = 0router.get('/abtest', (req, res) => {let color = '#2a2'visitorCount2 = visitorCount2 + 1if (visitorCount2 % 5 === 0) {// happens 1 every 5 timescolor = '#a22'}res.send(`<style>h1 {color: ${color};}</style><h1>Hello World</h1>`)})
Now you know how to send requests using the browser url or fetch
as well as
listening to incoming requests and sending responses. In this section you will
explore a few important response headers and how it affects the browser.
To set a response header, we run the set
function in the response object
before sending back a response. set
takes in an object of the properties we
want to set.
To read a request header, we can run the get
function in the request object
and pass in the string of the header we want.
In the request below, the first path is sending back a set-cookie
response
header to tell the browser to set a header.
The second path tells the user what cookie they have.
// index.jsconst express = require('express') // Import the express libraryconst app = express() // Use the express library to create an appapp.get('/set', (req, res) => {res.set({'set-cookie': 'name=c0d3js5' // For cookies, you must set a key / value pair})res.send('<h1> Cookie has been set </h1>')})app.get('/', (req, res) => {// You may notice that cookie is a long string of cookies// split by ;// You may need to parse out the cookie key / value to// get the correct value.res.send(`<h1>Welcome ${req.get('cookie')}</h1>`)})app.listen(3000) // Your app needs to listen to a port number.
If you go to the /set
path, you should notice that the response has a
set-cookie header.
All requests that your browser sends to your domain will have a cookie
header
in the request.
If you want to tell the browser to save the response for a period of time so it
does not send a request to you, you can send back a Cache-Control
header with
a value of max-age=120
. This will tell the browser to save the response for
120 seconds.
If you have a website that millions of people use daily, your application will
receive millions of requests every day. Setting cache-control
headers in your
response can help you reduce the number of requests you receive.
When you first load the url
in the code below, you will notice it will take a
long time before receiving the response. The response would have a
cache-control
header. If you open a new tab to the same url, you will notice
that the page loads instantly, meaning the browser did not send the request.
You can also send a fetch
request to the url and see that the network request
gets the response instantly.
app.get('/ignore', (req, res) => {// This helps you see if your server actually recieved a request or not.console.log('ouch', Date.now())res.set('Cache-Control', 'max-age=120')setTimeout(() => {res.send('<h1>Cached page</h1>')}, 3000)})
Depending on which browser, display hello world in red or blue (chrome vs firefox)
const browserHTML = (browser = 'Unknown') => {let color = '#aaa'if (browser === 'firefox') {color = '#a22'}if (browser === 'chrome') {color = '#22a'}if (browser === 'safari') {color = '#2a2'}return `<style>h1 {color: ${color};}</style><h1>Welcome ${browser} user</h1>`}router.get('/browser', (req, res) => {const ua = req.get('user-agent').toLowerCase()if (ua.includes('firefox/')) {return res.send(browserHTML('firefox'))}if (ua.includes('chrome/')) {return res.send(browserHTML('chrome'))}if (ua.includes('safari/')) {return res.send(browserHTML('safari'))}return res.send(browserHTML())})
CORS: Allow browsers in other domains to send a request to your server for
all /api/*
paths. Remember, this would be an options request described in
the above section.
router.options('/api/*', (req, res) => {res.header('Access-Control-Allow-Credentials', true)res.header('Access-Control-Allow-Origin', req.headers.origin)res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, PATCH, DELETE')res.header('Access-Control-Allow-Headers','Origin, X-Requested-With, Content-Type, Accept, Credentials')res.send('ok')})router.put('/api/*', (req, res) => {res.send('<h1>put request received</h1>')})router.post('/api/*', (req, res) => {res.send('<h1>post request received</h1>')})
Show how many distinct users have visited a site - The industry standard is to use cookies to track users.
If you delete your cookie and refresh the page, you should see the distinct count go up.
Screenshot is dev console in Chrome
When setting cookie header, your string must have the key=value
format.
const { v4: uuid } = require('uuid')let uniqueVisitors = 0router.get('/distinct', (req, res) => {const cookie = req.get('cookie') || ''const cookieStr =cookie.split(';').find(str => {return str.includes('guid=')}) || ''let guid = cookieStr.split('=')[1]if (cookieStr) {return res.send(`<h1>You have been identified with guid ${guid}</h1><h3>Distinct Number of Visitor Count: ${uniqueVisitors}.</h3>`)}uniqueVisitors = uniqueVisitors + 1guid = uuid()res.set('set-cookie', `guid=${guid}`)res.send(`<h1>You have been ASSIGNED a guid ${guid}</h1><h3>Distinct Number of Visitor Count: ${uniqueVisitors}.</h3>`)})
AB Testing - 1 in 3 visitors who visits your website will always see "Hello World" in red. The rest will see it in blue. (You must delete cookie, use incognito mode, or open up a new browser to simulate a new visitor)
Setting visitor cookie for AB testing is important because you want to give select users the new experience throughout all the pages they visit. You can use cookies to set what treatment you want to give the user. In our case, we are giving 30% our users the red treatment.
let visitorId = 0router.get('/ab', (req, res) => {const cookie = req.get('cookie') || ''const cookieStr =cookie.split(';').find(str => {return str.includes('abtest=')}) || ''let visitorKey = cookieStr.split('=')[1]if (!visitorKey) {visitorKey = visitorIdvisitorId = visitorId + 1}let color = '#2a2'if (+visitorKey % 3 === 0) {// happens 1 every 3 timescolor = '#a22'}res.set('set-cookie', `abtest=${visitorKey}`)res.send(`<style>h1 {color: ${color};}</style><h1>Hello World</h1>`)})
Protip: nodemon is an application that helps you reload your JavaScript file automatically when you change a file so you don't have to do it manually. To run your JavaScript file with nodemon, follow the two steps:
npm i -g nodemon
nodemon app.js
This section will cover the important properties and functions in the request and response object that you may encounter at work.
Remember the URL consists of the following parts:
Sometimes, you can specify for the path itself to be a variable by adding a :
in front of the variable name
// Visit https://pathacks.com:4098/users/billyjoel/profile...app.get('/users/:username/profile/:age', (req, res) => {const userId = req.params.usernameres.send( `<h1>Welcome ${userId}, Your are ${req.params.age}</h1>` )})app.listen(4098)
Properties
The important properties in the request object that you should know are:
params
- An object with key / value pair based on the variables within the
URL.
query
- An object with key / value pairs based on the url query
parameters
hostname
- The hostname in the url
ip
- The address of the machine that sent the request.
ip
property will be the ip
address of the proxy, not the ip
address of the device that actually sent
the request. To get the original ip address, you must look at the
x-forwarded-for
request headers.headers
- Request headers. Normally we don't access headers directly using
this property, we use get
function to retrieve the headers we need
(described below)
body
- The body of the incoming request. This is only applicable to requests
methods that sends data in the request body: PUT
, PATCH
, POST
Functions
get
- Allows you to retrieve to request headerreq.get('authorization')
will return the authorization value in the
Authorization
header.req.get('x-forwarded-for')
will return the ip address of the original
machine that sent the request if the request went through a proxy.When it comes to the response object, it is important to know the functions.
send
- Takes in a string and sets the response body and sends the response.
res.send('<h1>hello</h1>')
status
- Takes in a number and sets the status of the response. Defaults
to 200. Returns the original response object
res.status(400).send('wrong input')
redirect
- Takes in a url string and sets the response status code to 302.
When the browser receives the response, it will send a new request to the url
you specified
res.redirect('https://google.com')
set
- Sets a response header. There are two ways to use this.
// sets the set-cookie header in the response to hello.res.set('set-cookie', 'hello')// THis method of setting header is useful when you want to// set more than 1 header at onceres.set({'set-cookie': 'hello','cache-control': 'max-age=120'})
json
- Sets the response header content-type
to JSON and sends back a JSON
string. Usually used for APIs to send back data.
res.json([{title: 'lesson1'},{title: 'lesson2'}])
sendFile
- Takes in a file path and sends back a file.
res.sendFile('./funny.png')
- sends back an image. Make sure the file
exists!For some of the exercises below, you would have to come up with your own paths that makes sense to you. You should be practicing how to combine your front end skills with the backend to be able to build complete projects.
Custom Delay: When user
visits /delayed?time=2000
, delay the welcome
response by the specified
number of milli-seconds in the url parameter
router.get('/delayed', (req, res) => {setTimeout(() => {res.send(`<h1>Here is your response after ${req.query.time}ms delay</h1>`)}, +req.query.time)})
Guest Note: When users visit
/messages
, they should see a list of messages. They can also add messages.
When server restarts, you must not lose your data. Since you did not learn
how to handle request body on the server, send the data in the url query
parameter.
const noteFile = './notes'let notes = []fs.readFile(noteFile, (err, data) => {if (err) {return console.log('error reading file')}const str = data.toString()if (str) {notes = JSON.parse(str)}})router.get('/notes', (req, res) => {const noteListString = notes.reduce((acc, note) => {return (acc +`<h3>${note}</h3>`)}, '')res.send(`<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/andybrewer/mvp/mvp.css" /><main>${noteListString}<hr /><textarea class="noteInput" name="" cols="30" rows="10"></textarea><button class="noteSubmit">Submit</button><script>const textarea = document.querySelector('.noteInput')const submit = document.querySelector('.noteSubmit')submit.addEventListener('click', () => {const value = textarea.valuefetch('./notes/add?content=' + value)textarea.value = ''alert('submitted. Refreshing the page to see your message')window.location.reload()})</script>`)})router.get('/notes/add', (req, res) => {const content = req.query.contentif (!content) {return res.status(400).send('Please provide a content query parameter')}notes.unshift(content)notes = notes.splice(0, 5)fs.writeFile(noteFile, JSON.stringify(notes), () => {})res.json(notes)})
Online Users: When user
visits /online?name=joe
user should see a list of all online users.
Users will always get 401 response until they put in the name query parameter
Display a list of users who are online (send a request every 1 second)
const lastseen = {}router.get('/online', (req, res) => {const name = req.query.nameif (!name) {return res.status(401).send('Please set a query params with name as the key and your name as the value')}lastseen[name] = Date.now()res.send(`<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/andybrewer/mvp/mvp.css" /><main><h1>Welcome ${name}</h1><p>Open this page in another tab, use a different name!</p><div class="container"></div></main><script>const container = document.querySelector('.container')const render = (data) => {console.log('data is', data)const otherUserString = data.reduce((acc, name) => {return acc + '<h1>' + name + '</h1>'}, '')if (otherUserString) {container.innerHTML = '<h2>Other Users</h2>' + otherUserString}}const getData = () => {fetch('./users?name=${name}').then(r => r.json()).then(data => {console.log('data is', data)render(data)setTimeout(() => {getData()}, 1000)})}getData()</script>`)})router.get('/users', (req, res) => {const authorName = req.query.nameconst now = Date.now()lastseen[authorName] = now // update the user that sent the requestObject.keys(lastseen).forEach(name => {if (lastseen[name] < now - 1000 * 10) {// if last seen is > 10 seconds, removedelete lastseen[name]}})const online = Object.keys(lastseen).filter(name => {return name !== authorName})res.json(online)})
Heartbeat is a concept where you send a request to the server constantly to get data or simply let the server know you are still connected.
In the previous examples, your request handlers only used two arguments: request and response. You can actually use a third argument, which is a function.
// Middleware to make sure every request has a cookie in order// to access the profile page.app.get('/profile',(req, res, next) => {if (!req.get('cookie')) {return res.status(401).send('You are not authorized to visit this page')}next()},(req, res) => {const username = req.get('cookie')res.send(`<h1> Welcome ${username}!</h1>`)})
In the example above, we passed 3 arguments into app
next()
runs the next function.The function that checks if the request has a cookie header is called a middleware. A middleware is a concept where a request goes through a function and before reaching the main function. The function in the middle is called a middleware.
A middleware is simply a concept. It is not a special function or anything. It is simply a label.
The simplest middleware is the request handler in the example above.
Sometimes, you may want to run your middleware on different paths and different
request methods. In the example below, the middleware will run for all request
methods on all paths that starts with /api
.
// Middleware to make sure every request has a cookie in order// to access the profile page.app.use('/api/*', (req, res, next) => {if (!req.get('cookie')) {return res.status(401).send('You are not authorized to visit this page')}next()})app.get('/api/users', (req, res) => {const username = req.get('cookie')res.send(`<h1> Welcome ${username}!</h1>`)})
When you use app.use('/hello', requestHandlerFn)
, all request methods will go
through the requestHandlerFn
: GET
, POST
, PATCH
, PUT
, DELETE
.
If you do not pass in a path as the first argument,
app.use( requestHandlerFn )
, that means that requestHandlerFn
will be run
for all request methods on all paths.
Since objects are non-primitive, your middleware can also set a property on the request. All the following request handlers will be able to use the request property.
let count = 0app.get('/', (req, res, next) => {req.user = {id: count}count = count + 1next()})app.get('/', (req, res) => {res.send(`<h1> You are visitor number ${req.user.id}!</h1>`)})
At many companies, a common practice is to have a middleware to set a user
object into the request object so all request handlers can access user info
(like username, name, location, etc.) through req.user
.
There are a few middlewares that are used frequently.
Express.static
This middleware will create a path for every file in your public folder. If you
have a picture called c0d3.png
in the public
folder, you can access the
picture by going to
app.use(express.static('./public'))// Question, what does express.static(...) return?
Since middlewares are request callbacks, express.static(...)
returns a
function.
In the previous examples, we used req.get('cookie')
to retrieve cookies and we
also set cookie headers in the response ourselves. It takes alot of work to do
cookies correctly, and express-session middleware helps you do it right! Here
are a few problems it solves:
Cookies are insecure. To determine the logged in user sending the request, you cannot simply store the username in the cookie. If you did that, then any user could change the cookie value to be anyone else. Instead, you must secure it by encrypting the cookie you want to set, and decrypting the cookie from the request to get user's information.
Parsing cookies are not easy. Taking a string, separating the string by ;
and then getting out the key/value takes alot of effort. Express session helps
you parse it out and gives you a request.session
middleware.
// setting up the middlewareapp.use(session({secret: 'Your Encryption String',resave: false,saveUninitialized: true,cookie: { secure: true }}))// ... Using the session from the request ...app.get('/*', (req, res) => {// getting a session valueconsole.log( req.session.name )// setting a session valuereq.session.name = 'hello....'...})
Express.json
This middleware puts a body
property in the request object when the request
has JSON string data; a content-type
header of application/json
and a body
string.
app.use(express.json({ limit: '10mb' }))
With this middleware, your request handler will be able to get the request body
from request methods that has a request body: POST
, PUT
, POST
.
Note This middleware is only applicable for application/json
content type.
There are other content-types like text, files, forms, etc. To handle those you
have to add additional middleware.
This middleware helps you process file uploads and puts a files
property in
the request object when a user uploads a file.
const multer = require('multer')// You must specify a folder where files should get uploaded to// Tip: If you specify the same folder as express.static middleware// then every file you upload will be available on the websiteconst upload = multer({ dest: 'uploads/' })// A form used for uploading file requires a key / value pair.// Below, we are assuming the key is called keynameapp.post('/files', upload.array('keyname'))
Example: Drag and Drop front end
e.preventDefault
on your drag events listeners in order for the
drop
event to work.drop
event fires, you can get the files from the event objectnew formData()
and files. Make sure the
array key matches your server middleware name. In our example, we are using
keyname
.<style>.container {position: fixed; top: 0; bottom: 0; left: 0; right: 0;}.container.dropping {background-color: #8f8;}</style><div class="container"></div><script>const container = document.querySelector('.container')const makeGreen = (e) => {container.classList.add('dropping')container.innerHTML = "<h1>Will upload " + e.dataTransfer.items.length + " Files</h1>"e.preventDefault()}const clearScreen = (e) => {container.classList.remove('dropping')container.innerHTML = ""e.preventDefault()return false}console.log('container', container)document.body.addEventListener('dragover', makeGreen)document.body.addEventListener('dragleave', clearScreen)document.body.addEventListener('drop', (e) => {e.preventDefault()const files = Array.from(e.target.files || e.dataTransfer.files)if (!files.length) {return}const formData = new FormData()files.forEach( file => {formData.append('keyname[]', file, file.name)})fetch('/files', {method: 'POST',body: formData}).then( r => r.json() ).then(arr => {window.location.reload()})return clearScreen(e)})</script>
Todo API: Build a fully functional todo list API that follows the REST convention. Use the uuidv4 library to generate unique id for each todo item.
Picture Saver: Starting with a webcam app, send a POST request to your server with the image data to create a picture that you can download.
For now, don't worry about building the webcam app yourself, just right click, view source, and then copy that into your own file.
Look at the network request, you want to change the url in the fetch
request to be your own url.
The response you get back will contain the url to the image that you took.
The image data is simply a string, in base64 encoding. Therefore, when you
write the image to a file, you must specify that it is a base64
string.
fs.writeFile( imagePath, req.body.img, 'base64', (err) => {...} )
Industry standards for building authentication.
When the user inputs their information (like username / password) on their browser, make sure the password input field has the correct type. This way, the password is never revealed on the screen.
<input type="password" />
(optional) When the user hits the submit button, make sure to encrypt the password. You don't want an accident where you are looking at the network request with your coworker and accidentally see your password. Many companies don't do this.
When sending a network request to the server, make sure to send the data via a POST request so the data is in the body. If you use a GET request, the user's username and password will be exposed to anybody who can see the network requests.
fetch('/api/sessions', {method: 'POST',headers: {'content-type': 'application/json',}body: JSON.stringify({username, password})})
When you receive a user's password on the server, you should not store the user's password directly into your database. Instead, you need to do a one way encryption. This means that even you yourself will not be able to decrypt the stored user's password. To verify that a user's password is correct, you run the one way encryption on the password and check to see if the encrypted value matches what is in the database. The higher the salt level, the slower the encryption. The most well know library to do this is bcrypt.
// During signup / password reset:bcrypt.hash(user_password, SALT_ROUNDS, (err, hashPw) => {// hashPw will be the encrypted password})// During login, when we want to compare the passwordsbcrypt.compare(user_password, hashPw, (err, result) => {// if passwords matches, result will be truthy})
When you send back the user data as a response or console.log the user information, make sure to remove the password field from the user before logging. A team at facebook did not pay attention to this and as a result, millions of user account passwords are exposed.
delete user.passwordconsole.log(user)
Congratulations! You know enough to tackle the challenges. Good luck!
In the previous lessons you tested functions. Those tests are called unit tests. You treat each function like a unit of code and you are testing each function (aka unit) of code.
In this lesson we will learn about integration test. Integration test is when you write tests that start a server, send a request, and expect response to be a certain way. When you send a request, the server request handler could be running many many different functions before sending back a response. Integration tests are appropriately named because it is making sure all your functions are properly integrated with each other.
To write your code in a way that can be tested well, you must provide two
functions: startServer
and closeServer
that could be run in the beginning
and the end of your test, respectively.
startServer
will take in a port, but the port may be unavailable. Close the
connection and try with the next port number until you are able to successfully
connect to a port.
app.listen
takes in an optional second argument, a function, that runs when
the connection is done. Our startServer function will use this to return a
promise that resolves.
const startServer = port => {return new Promise((resolve, reject) => {app.listen(port, e => {console.log(`app successfully listening to port ${port}`)})})}
Functional Tests - UI testing. Example we use: https://www.cypress.io/
To make sure that the urls will receive requests and respond correctly, you will write integration tests. To write an integration test, you need to make sure you have a function to start and stop your server.
const startServer = (port) => {return new Promise( (resolve, reject) => {...const server = app.listen(port)resolve(server)})}const stopServer = (server) => {return new Promise( (resolve, reject) => {server.stop( () => {resolve()})})}module.exports = {start: startServer,stop: stopServer}
Since starting and stopping server takes time, you need to make sure your test
doesn't start until the server is completely started by using beforeAll()
.
Also you need to make sure you don't stop the server until your tests are done
by using afterAll()
.
//Test filedescribe('test API', () => {beforeAll( () => {startServer()})afterAll(() => {stopServer()})it('should get all students', () => {fetch(...)})it('should create a student', () => {fetch(...)})it('should update a student', () => {fetch(...)})it('should delete a student', () => {fetch(...)})})
Deploy your application to freedomains.dev and share your application with the world!
Complete JS5 challenges