Full Stack Open Submissions
It is possible to make a backend with node js, runtime based on Google's Chrome V8 JS engine. Browsers don't yet support the newest features so that the code must be transpiled with e.g. babel. The situation with JavaScript running in the backend is different. The newest version of Node supports a large majority of the latest features of JavaScript, so that, the latest features can be used without having to transpile the code.
Note
Applications and exercises in this part are not all React applications, vite@latest -- --template react
utility won't be used for initializing the project for this application.
-
Create a new npm application:
npm init
-
In
package.json
change scripts object by adding a new script command:"start": "node index.js",
-
To initialize a web server, edit index.js as follow:
import http from 'http' const app = http.createServer((request, response) => { response.writeHead(200, { 'Content-Type': 'text/plain' }) response.end('Hello World') }) const PORT = 3001 app.listen(PORT) console.log(`Server running on port ${PORT}`)
-
Run application:
npm start
-
Make web server offering raw data in JSON format:
import http from 'http' let notes = [{ id: "1", content: "HTML is easy", important: true }, { id: "2", content: "Browser can execute only JavaScript", important: false }, { id: "3", content: "GET and POST are the most important methods of HTTP protocol", important: true }] const app = http.createServer((request, response) => { response.writeHead(200, { 'Content-Type': 'application/json' }) response.end(JSON.stringify(notes)) }) const PORT = 3001 app.listen(PORT) console.log(`Server running on port ${PORT}`)
-
Restart server:
ctrl+c
,npm start
Install express: npm i express
import express from 'express'
const app = express()
app.get('/', (request, response) => {
response.send('<h1>Hello World!</h1>')
})
app.get('/api/notes', (request, response) => {
response.json(notes)
})
The event handler function accepts two parameters. The first request parameter contains all of the information of the HTTP request, and the second response parameter is used to define how the request is responded to.
nodemon will watch the files in the directory in which nodemon was started, and if any files change, nodemon will automatically restart your node application.
npm install --save-dev nodemon
Dedicated npm script for it in the package.json file:
{
// ..
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
// ..
}
Representational State Transfer, aka REST, was introduced in 2000 in Roy Fielding's dissertation. REST is an architectural style meant for building scalable web applications.
One convention for creating unique addresses is to combine the name of the resource type with the resource's unique identifier.
URL | verb | functionality |
---|---|---|
notes/10 | GET | fetches a single resource |
notes | GET | fetches all resources in the collection |
notes | POST | creates a new resource based on the request data |
notes/10 | DELETE | removes the identified resource |
notes/10 | PUT | replaces the entire identified resource with the request data |
notes/10 | PATCH | replaces a part of the identified resource with the request data |
The unique address we will use for an individual note is of the form notes/10, where the number at the end refers to the note's unique id number.
The id parameter in the route of a request can be accessed through the request object:
app.get('/api/notes/:id', (request, response) => {
const id = request.params.id
const note = notes.find(note => note.id === id)
if (note) {
response.json(note)
} else {
response.status(404).end()
}
})
Since no data is attached to the response, we use the status method for setting the status and the end method for responding to the request without sending any data.
Tip
The if-condition leverages the fact that all JavaScript objects are truthy, meaning that they evaluate to true in a comparison operation. However, undefined is falsy meaning that it will evaluate to false.
Deletion happens by making an HTTP DELETE request to the URL of the resource:
app.delete('/api/notes/:id', (request, response) => {
const id = request.params.id
notes = notes.filter(note => note.id !== id)
response.status(204).end()
})
There's no consensus on what status code should be returned to a DELETE request if the resource does not exist. For the sake of simplicity, the application will respond with 204 in both cases.
To access the data easily, we need the help of the Express json-parser that we can use with the command app.use(express.json())
.
app.use(express.json())
app.post('/api/notes', (request, response) => {
const note = request.body
console.log(note)
response.json(note)
})
The event handler function can access the data from the body property of the request object.
Warning
A potential cause for issues is an incorrectly set Content-Type header in requests. This can happen with Postman if the type of body is not defined correctly
HTTP standard:
- Safety:
GET
andHEAD
methods should not have the significance of taking an action other than retrieval. By side effects, it means that the state of the database must not change as a result of the request, and the response must only return data that already exists on the server. - Idempotency: All HTTP requests except
POST
should be idempotent, it refers to the property where making the same request multiple times has the same effect as making it once. This is important for ensuring that repeated requests do not cause unintended side effects.
Tip
HEAD
should work exactly like GET
but it does not return anything but the status code and response headers. The response body will not be returned when you make a HEAD
request.
Middleware are functions that can be used for handling request and response objects e.g. the previous express.json()
parser is a middleware.
In practice, several middlewares can be used at the same time ; they're executed one by one in the order that they were listed in the application code.
Middleware is a function that receives three parameters: (request, response, next)
. The next
function yields control to the next middleware.
-
Part0:
- Vite: By default,
index.html
does not contain HTML markup visible by human. - JS: ES6 introduced the arrow functions
const arrow = () => {}
.
- Vite: By default,
-
Part1:
- JSX: Any JS code in curly brace in evaluated.
- JSX: Always render a root element.
- JS: The
reduce()
method executes a reducer function for array element, and it returns a single value: the function's accumulated result. - JS: Use
let
for reassignable variables andconst
for variables that shouldn’t change. - JS: Arrow functions are more concise and do not bind
this
. - JS: Key-value pairs:
{ key: value }
. Properties can be accessed using dot notation (obj.key
) or bracket notation (obj['key']
). - JS: Destructuring extracts values from arrays or objects into variables:
- Arrays: const [a, b] = array.
- Objects: const { key } = object.
-
Part3:
-
3.1:
-
Install express, dotenv (PORT variable in
.env
file) and nodemon (as dev dependency). -
Create
index.js
;-
Create
contacts
list. -
Create
app
object withexpress()
-
Create
get
route:app.get('/api/persons', (req, res) => { res.json(contacts) // response.json -> a json object })
-
And start listening:
app.listen(PORT, () => { // use port env variable console.log(`Server listening on port:${PORT}`) })
-
-
-
3.2:
- Create a second
get
route for the info endpoint and use thesend()
method (instead ofjson()
) to return html content.
- Create a second
-
3.3:
-
Create the third
get
route for displaying the data for a person if this one exists in the hardcoded list:app.get('/api/persons/:id', (req, res) => { const searchedId = req.params.id const searchedPerson = contacts.find(c => c.id === searchedId) if (searchedPerson) { res.json(searchedPerson) } else { res.status(404).end() } })
-
Used system dynamic variables feature offered by REST client to generate random requests:
GET http://localhost:3000/api/persons/{{$randomInt 1 6}}
-
-
3.4:
- Create
delete
route. - Fetch id param from
req.params
object. - Filter list based on this id.
- Return status code 204.
- Create
-
3.5:
-
Use body parser:
const app = express() // use express app.use(express.json()) // use body parser (needed for POST)
-
The new generated ids will be of
Number
type. Since that, it's necessary to convert types before comparison e.g.app.get('/api/persons/:id', (req, res) => { const id = Number(req.params.id) const searchedPerson = contacts.find(c => Number(c.id) === id) if (searchedPerson) { res.json(searchedPerson) } else { res.status(404).end() } })
-
-
3.6:
-
Check manually business rules:
if (!req.body.name || !req.body.number) { return res.status(400).json({ error: 'name and number musn\'t be missing' }) } if (contacts.filter(c => c.name === req.body.name).length > 0) { return res.status(400).json({ error: 'name must be unique' }) }
-
-
3.7:
- Install and import morgan.
- take morgan into use:
app.use(morgan('tiny')) // use morgan middleware
.
-
-
Create dev script :
nodemon index.js
and run.