fullstackopen part3_nodejs and express (RESTfull Http Api as jason-server)
Create simple server using node built-in http web server.
Output Listen to port3001
Create file header content-type: html/text print hardcoded response.end .
Create file header content-type: 'Content-Type': 'application/json' print hardcoded response.end json format.
Install node express and nodemon.
npm install express npm install --save-dev nodemon { // .. "scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
}, // .. }
npm run dev
.Use parameter id from route to pass to application to find the data. .Implement error status code 404 for data not found
.Delete data using id .Install' plugin VS code REST Client for on board API data view.
.Run server Request in REST Clent editor Get http://localhost:3001/api/notes/2
.Add new note. .Auto generate header content-type: with the help of json-parser / app.use(express.json()). .Retrive data from body property of the request object.
.Create hard code data object to add new note.
.Heroku app
.Heroku express api/notes
.Heroku express frontend build and backend
.Front end proxy to backend localhost:3001/api/notes
-
heroku app for beckend
https://afternoon-plateau-39207.herokuapp.com/
-
Create new production build on the frontend and make a copy to backend.
-
push the changes to heroku
git push heroku main
- To run heroku with mongoDB
heroku config:set MONGODB_URI=mongodb+srv://fullstack_amutha:(secretpassword)@cluster0.eqxje.mongodb.net/note-app?retryWrites=true&w=majority
If it causes error. set apostrophes for the MONGODB_URI's value.
- view the database in heroku
https://afternoon-plateau-39207.herokuapp.com/
Part 4
- Establish file structure for separate responsibilities of the application into separate module
File structure of this backend project before separating the app into different modules : -
File structure of this backend project after separating the app into different modules : -
npm install --save-dev jest
Edit npm npm scripts test : - Execute tests with Jest and to report about the test execution with the verbose style:
"test": "jest --verbose"
Jest requires one to specify that the execution environment is Node. This can be done by adding the following to the end of package.json:
"jest": { "testEnvironment": "node" }
- Using
integration testing
where there are multiple components of the system being tested.
- Define the execution mode of the application with the NODE_ENV environment variable
{ // ... "scripts": { "start": "NODE_ENV=production node index.js", "dev": "NODE_ENV=development nodemon index.js", "build:ui": "rm -rf build && cd ../../../2/luento/notes && npm run build && cp -r build ../../../3/luento/notes-backend", "deploy": "git push heroku master", "deploy:full": "npm run build:ui && git add . && git commit -m uibuild && git push && npm run deploy", "logs:prod": "heroku logs --tail", "lint": "eslint .", "test": "NODE_ENV=test jest --verbose --runInBand" }, // ... }
There is a slight issue in the way that we have specified the mode of the application in our scripts: it will not work on Windows. We can correct this by installing the cross-env package as a development dependency with the command:
npm install --save-dev cross-env
- Edit config.js and .env file
const MONGODB_URI = process.env.NODE_ENV === 'test' ? process.env.TEST_MONGODB_URI : process.env.MONGODB_URI
TEST_MONGODB_URI=" "
Install supertest package to help us write our tests for testing the API.
Install the package as a development dependency:
npm install --save-dev supertest
Import supertest in test file.
const supertest = require('supertest')
run test file :
npm test -- test/note_api.test.js
-
test name
npm test -- -t "a specific note is within the returned notes"
-
test describtion
npm test -- -t 'notes'
The express-async-errors library has solution for this.
npm install express-async-errors
Import the library in app.js
require('express-async-errors')
- The 'magic' of the library allows us to eliminate the try-catch blocks completely.
- The library handles everything under the hood.
- If an exception(error) occurs in a async route, the execution is automatically passed to the error handling middleware.
Using promise.all
-
Promise.all executes the promises it receives in parallel.
-
In order for the promises to be executed in a particular order, the operation can be executed inside of a for...of block that executed in specific order.
-
Promise.all executes the promises it receives in parallel.
-
In order for the promises to be executed in a particular order, the operation can be executed inside of a for...of block that executed in specific order.
The Promise.all method can be used for transforming an array of promises into a single promise, that will be fulfilled once every promise in the array passed to it as a parameter is resolved. The last line of code await Promise.all(promiseArray) waits that every promise for saving a note is finished, meaning that the database has been initialized.
The returned values of each promise in the array can still be accessed when using the Promise.all method. If we wait for the promises to be resolved with the await syntax const results = await Promise.all(promiseArray)
, the operation will return an array that contains the resolved values for each promise in the promiseArray, and they appear in the same order as the promises in the array.
Promise.all
executes the promises it receives in parallel. If the promises need to be executed in a particular order, this will be problematic. In situations like this, the operations can be executed inside of a for...of
block, that guarantees a specific execution order.
beforeEach(async () => { await Note.deleteMany({})
for (let note of helper.initialNotes) { let noteObject = new Note(note) await noteObject.save() } })
- This is task using MongoDB document database.
To create user password hash install :
npm install bcrypt
Mongoose does not have a built-in validator for checking the uniqueness of a field. In order to have a unique username we install ready-made solutions from mongoose-unique-validator npm pakage.
npm install mongoose-unique-validator
npm instal jsonwebtoken
Create code for the function in controllers/login.js
.
The process for the new note is : -
STEPS
- Create user
controllers/users.js
- Create token
controllers/login.js
using the user and password. - Create new note with token from step 2. 'controllers/notes.js` using token and bearer scheme
- Bearer scheme is necessary for server to offer multiple ways to authenticate. Attach credentials ..
The token can be faulty (like in our example), falsified, or expired. Let's extend our errorHandler middleware to take into account the different decoding errors. Using middleware to handle decoding errors.
(error.name === 'JsonWebTokenError') { return response.status(401).json({ error: 'invalid token', }) }
If the application has multiple interfaces requiring identification, JWT's(jswebtoken) validation should be separated into its own middleware. Some existing library like express-jwt could also be used.
Problems of Token-based authentication
-
Downside of token is it has blind trust to the token holder. It allow a user who access has been denied to still use the token. For this reason we can limit the validity period of the token.
-
const token = jwt.sign(userForToken, process.env.SECRET, { expiresIn: 30 * 30, })`
The client has to get new token once the token expire. We use middleware to handle the expired token error.
Option two is to create a server side session. Saving the token infor in backend datebase and check for API request for access right.
The downside for server side session is it increase the complexity and performance since the token validity needs to be checked for each API request from database which considered slower compare to checking validity from token itself.
Error Message
`Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles
to troubleshoot this issue.`