You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
613 lines
18 KiB
Markdown
613 lines
18 KiB
Markdown
# Express Basics
|
|
|
|
# JS HTTP server
|
|
|
|
# A simple HTTP server in Node
|
|
|
|
__A simple server in three steps:__
|
|
|
|
__include "http" library__
|
|
|
|
__create server responses__
|
|
|
|
__define the port that you want to listen to.__
|
|
|
|
__Note: The \_ before a variable name is used to indicate that the variable isn't used. This is to avoid the VSCode warning about unused variable__
|
|
|
|
import http from 'http'
|
|
|
|
const server = http.createServer((\_req, res) => {
|
|
|
|
res.write('Some cool response!')
|
|
|
|
res.end()
|
|
|
|
})
|
|
|
|
const port = 3000
|
|
|
|
server.listen(port)
|
|
|
|
console.log('HTTP Server listening port', port)
|
|
|
|
For this code to work, remember "type": "module" in your package.json. Also, you may need to change your port number to something other than 3000.
|
|
|
|
# PORT numbers
|
|
|
|
__The port number (16 bit unsigned integer) defines a port through which the communication to your application happens. Ports allow multiple applications to be located on the same host (IP).__
|
|
|
|
__The client connects to this port in some server (defined by an IP address), so the client and the server are able to communicate.__
|
|
|
|
__Computers have several different ports at their disposal __ _but_ __ some of them are being used and your application might not be able to assign those ports to itself.__
|
|
|
|
__Ports from 0 to 1023 are the system ports. There are restrictions in manually assigning applications to use them.__
|
|
|
|
__Generally speaking, ports from 1024 to 65535 are available for user to assign applications to use them.__
|
|
|
|

|
|
|
|
# Request and response
|
|
|
|
__The request object is an object that is sent__ __ from the client to the server.__
|
|
|
|
__The response object is an object that is sent__ __ from the server to the client__ __. When you modify the response object, you're essentially modifying how the server responds to the client.__
|
|
|
|
_const_ server = http.createServer(( _req_ , _res_ ) _=>_ {
|
|
|
|
res.write("Some cool response!");
|
|
|
|
res.end();
|
|
|
|
});
|
|
|
|
# Exercise 1: Simple Request & Response
|
|
|
|
Create a basic HTTP-server application. The application should respond to GET requests by returning a response with the following content: "Hello world!".
|
|
|
|
Test your application either with _Postman,_ or by navigating your browser to http://localhost:< _your\_portnumber_ >
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# Express Server
|
|
|
|
# Express
|
|
|
|
__Express is the most popular server-library for Node__
|
|
|
|
__Setting up a server with node-http is already fairly simple, but Express tries to make this even simpler__
|
|
|
|
__In some cases you can manage without Express, but for larger applications it's recommended__
|
|
|
|
__Install Express by using__
|
|
|
|
__npm install express__
|
|
|
|
# A side note about dependencies
|
|
|
|
* __By using __ __npm install <package>__ __, the package will become dependency for the application.__
|
|
* __By using __ __npm install --save-dev <package>__ __, the package will become __ __development dependency__ __ for the application.__
|
|
* __This means the package won't be included in the built application.__
|
|
* __A shortcut for __ __--__ __save-dev__ __ is __ __-D__
|
|
* __Dependencies are needed to run the application, dev-dependencies are needed for development.__
|
|
* __Additional info: __ [https://www.geeksforgeeks.org/difference-between-dependencies-devdependencies-and-peerdependencies/](https://www.geeksforgeeks.org/difference-between-dependencies-devdependencies-and-peerdependencies/)
|
|
|
|
# Getting started with Express
|
|
|
|
__Create a very simple express project __
|
|
|
|
import express from 'express'
|
|
|
|
const server = express()
|
|
|
|
server.listen(3000, () => {
|
|
|
|
console.log('Listening to port 3000')
|
|
|
|
})
|
|
|
|
__Then start the server and connect to __ [http://localhost:3000](http://localhost:3000) __ with your browser.__ __ (See instructions on the next slide)_
|
|
|
|
# Starting the server
|
|
|
|
__To make using the import statement possible, we need to edit the __ __package.json.__
|
|
|
|
__Add a new script "start" with the value "node index.js" under "scripts" object in the package. json__
|
|
|
|
__(you can name index.js to whatever file your server code is in)_
|
|
|
|
__Add "type": "module" property__
|
|
|
|
__Now you can run the server with __ _npm start_
|
|
|
|
# Express - Requests
|
|
|
|
__The server does not yet __ _do_ __ anything except listens. That is because we have not defined how it should deal with incoming __ _requests._
|
|
|
|
__With Express you can easily process different kind of requests, e.g.,__
|
|
|
|
__server.get(), server.post(), server.delete() etc!__
|
|
|
|
__An example of a simple GET response below.__
|
|
|
|
server.get("/", (request, response) => {
|
|
|
|
response.send("Just saying hello!")
|
|
|
|
})
|
|
|
|
---
|
|
|
|
Käy läpi tämä esimerkki, ja anna sitten tehtäväksi ensinnäkin muuttaa portti ja palautusviesti muotoon "Hello world!". Tällä tavalla kaikkien pitää ymmärtää miten tämä toimii.
|
|
Anna heidän pähkäillä miten voi tehdä päätepisteen, johon pääsee näin localhost:5000/secondPage. Tämän jälkeen käy läpi yhdessä tämä heidän kanssa.
|
|
|
|
# Express - Endpoints
|
|
|
|
__To create a new __ _endpoint _ __we define what __ _kind_ __ of request we're handling, then what the __ _route_ __ to the endpoint is and finally what the endpoint __ _does_ __.__
|
|
|
|
__In the below example we are creating an endpoint that handles a __ __GET__ __ request to route __ __/foobar__ __, that sends a response containing the string 'OK'.__
|
|
|
|
server.get('/foobar', (req, res) => {
|
|
|
|
res.send('OK')
|
|
|
|
})
|
|
|
|
---
|
|
|
|
# Nodemon
|
|
|
|
__Reminder__ __: If you are not using Nodemon in development, you are making your life unnecessarily difficult. Use Nodemon. __
|
|
|
|
__Instructions are __ __below as a refresher.__
|
|
|
|
# Nodemon with JavaScript
|
|
|
|
One of the handiest packages out there is nodemon, which monitors your node program. Whenever you save your files, it reloads the program, showing you immediately the results of your changes.
|
|
|
|
Nodemon is installed as a dev dependency __npm install --save-dev nodemon__
|
|
|
|
To use nodemon, you should have a development script "dev": "nodemon ./index.js"
|
|
|
|
# Nodemon with TypeScript
|
|
|
|
To get Nodemon working with TypeScript we need additional package to run our TS code without waiting for it to compile. __npm install --save-dev nodemon ts-node__
|
|
|
|
After installing, nodemon works directly with .ts files. "dev": "nodemon ./index.ts"
|
|
|
|
# Exercise 2: Simple Express Server
|
|
|
|
Create a basic Express application. The application should respond to GET requests by returns a response with the following content: "Hello world!".
|
|
|
|
Extra: Add a new endpoint to your application. So, in addition to accessing _http://localhost:3000_ , try to send something back from _http://localhost:3000/endpoint2_ .
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# Request params & query
|
|
|
|
__If request params are defined, they're something user __ __must__ __ send to the server.__
|
|
|
|
__https://localhost:5000/ThisIsParam__
|
|
|
|
__Request query is __ __optional__ __ information that the user can send to the server.__
|
|
|
|
__https://localhost:5000?ThisIsQuery=123__
|
|
|
|
__Chaining query parameters:__
|
|
|
|
__https://localhost:5000?first=123&second=456__
|
|
|
|
# Request params & request query
|
|
|
|
app.get("/:name/:surname", ( _request_ , _response_ ) _=>_ {
|
|
|
|
_console_ .log("Params:");
|
|
|
|
_console_ .log(request.params);
|
|
|
|
_console_ .log("Query:");
|
|
|
|
_console_ .log(request.query);
|
|
|
|
response.send("Hello " + request.params.name);
|
|
|
|
});
|
|
|
|
app.listen(5000);
|
|
|
|
Try connecting via browser!
|
|
|
|
_localhost:5000_ _/John/Doe_
|
|
|
|
_localhost:5000_ _/John/Doe_ _?query1=123_
|
|
|
|
---
|
|
|
|
Johdatteleva esimerkki vielä ennen seuraavaa tehtävää.
|
|
|
|
# Request query & param types
|
|
|
|
__Request parameters and query parameters are always of type __ _string_ __. If a number is passed, it will be a string that contains a number. __
|
|
|
|
__The example on right will __ __always __ __return 404, since the strict equality __ item.id === id
|
|
|
|
__will never be true. This is because the__ __ item.id __ __is of type __ _number _ __and__ __ id __ __being of type __ _string_ __.__
|
|
|
|
import express from 'express'
|
|
|
|
const server = express()
|
|
|
|
const data = [
|
|
|
|
{ id: 1, name: 'John' },
|
|
|
|
{ id: 2, name: 'Jane' }
|
|
|
|
]
|
|
|
|
server.get('/:id', (req, res) => {
|
|
|
|
const id = req.params.id
|
|
|
|
const info = data.find(item => item.id === id)
|
|
|
|
if (info === undefined) {
|
|
|
|
return res.status(404).send()
|
|
|
|
}
|
|
|
|
res.send(info)
|
|
|
|
})
|
|
|
|
server.listen(3000)
|
|
|
|
# Exercise 3: Counter Server
|
|
|
|
Create an API that consists of only one endpoint:
|
|
|
|
/counter
|
|
|
|
Whenever you enter this endpoint from the browser, it
|
|
|
|
should respond with a JSON object with information on how many times the endpoint has been accessed.
|
|
|
|
Extra: Make it possible to set the counter to whichever
|
|
|
|
integer with a query parameter /counter?number=5
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# Exercise 4: Advanced Counter Server
|
|
|
|
Expand the API from the previous assignment by accepting a name through the counter endpoint: /counter/:name
|
|
|
|
When entering this endpoint, the server should return the count of how many times this named endpoint has been visited.
|
|
|
|
For example
|
|
|
|
Aaron enters /counter/Aaron 🠖 "Aaron was here 1 times"
|
|
|
|
Aaron enters /counter/Aaron 🠖 "Aaron was here 2 times"
|
|
|
|
Beatrice enters /counter/Beatrice 🠖 "Beatrice was here 1 times"
|
|
|
|
Aaron enters /counter/Aaron 🠖 "Aaron was here 3 times"
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# "url" library
|
|
|
|
__With the "url" library, you can parse url fields (i.e., requests) the client sends to your server.__
|
|
|
|
_import_ url from 'url';
|
|
|
|
// URL module usage example
|
|
|
|
_const_ adr = ' [http://localhost:5000/default.html?year=2017&month=february](http://localhost:5000/default.html?year=2017&month=february) '; // Define the address
|
|
|
|
_const_ q = url.parse(adr, true); // Parse through the address with url.parse() function.
|
|
|
|
_console_ .log(q.host); // returns 'localhost:5000'
|
|
|
|
_console_ .log(q.pathname); // returns '/default.html'
|
|
|
|
_console_ .log(q.search); // returns '?year=2017&month=february'
|
|
|
|
_const_ qdata = q.query; // returns an object: { year: 2017, month: 'february' }
|
|
|
|
_console_ .log(qdata.month); // returns 'february
|
|
|
|
# Express Middleware
|
|
|
|
# Middlewares
|
|
|
|
__Generally speaking, middleware is an application that does something in between some other applications. __
|
|
|
|
__There are tons of middleware libraries available for Express.__
|
|
|
|
__What this means in Express: with a middleware, you can manipulate the __ __the client request and server response __ __in the client-server communication.__
|
|
|
|
__Middleware__ __ functions are functions that have access to the __ __request__ __ object (__ _req_ __), the __ __response__ __ object (__ _res_ __), and the __ _next_ __ function in the application's request-response cycle. When the __ _next_ __ function is invoked, the next middleware function is executed.__
|
|
|
|
__Middleware can be defined as such__
|
|
|
|
const authCheck = (req, res, next) => {
|
|
|
|
if (req.headers.Authentication === undefined) { // Read the request object
|
|
|
|
res.status(401).send('Missing Authentication Header') // Modify the response object
|
|
|
|
} else {
|
|
|
|
next() // Execute the next middleware
|
|
|
|
}
|
|
|
|
}
|
|
|
|
__In Express, .use() method initializes a middleware (argument 2) in a given path (argument 1).__
|
|
|
|
__In the middleware, the next() function will jump to the next middleware or endpoint.__
|
|
|
|
server.use('/customers', authCheck)
|
|
|
|
server.get('/customers', (req, res) => {
|
|
|
|
// we know that Authentication header is present
|
|
|
|
...
|
|
|
|
})
|
|
|
|
# Exercise 5: Logger Middleware
|
|
|
|
Create a new project called Student Registry. We will be developing this program in stages during this lecture. For now it should have a single GET endpoint /students that returns an empty list.
|
|
|
|
Create a logger middleware function that is used in all the project's endpoints. The middleware should log
|
|
|
|
the time the request was made
|
|
|
|
the method of the request
|
|
|
|
the url of the endpoint
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# Middleware - Unknown endpoint (AKA 404)
|
|
|
|
As you might have noticed, all websites have some kind of a default functionality for unknown endpoints.
|
|
|
|
This can be handled with a middleware.
|
|
|
|
Unknown endpoint has to be taken into use after the actual endpoints!
|
|
|
|

|
|
|
|
# Middleware - Error handler
|
|
|
|
An error handler is often needed to handle different kind of user or application errors.
|
|
|
|
We can use middleware to enable error handling for our application.
|
|
|
|
Has to be the last middleware to be taken into use!
|
|
|
|

|
|
|
|
# Middleware - Req & Res
|
|
|
|
__A n__ __eed to manually alter req & res is quite common. To avoid bloated code, the handling can be easily done with proper middlewares.__
|
|
|
|
__Setting a header in middleware:__
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
res.setHeader('Content-Type', 'image/png')
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
})
|
|
|
|
__Getting a header and storing its value. If the header is not present, return 403 with an 'Unauthorized' message:__
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
const someIdNeededInApplication = req.header('Some-Custom-Header')
|
|
|
|
if (!someIdNeededInApplication) {
|
|
|
|
res.status(403).send('Unauthorized')
|
|
|
|
}
|
|
|
|
someIdFunctionality(someIdNeededInApplication)
|
|
|
|
next()
|
|
|
|
})
|
|
|
|
# Exercise 6: 404 Not Found
|
|
|
|
Add a middleware function that sends a response with status code 404 and an appropriate error message, if user tries to use an endpoint that does not exist.
|
|
|
|
If you have not already done so, move all your middleware to a separate file, to keep your program clean and readable.
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# Express.js: Creating a REST API
|
|
|
|
# REST HTTP requests
|
|
|
|
__We have gone through what a GET request is. Let's briefly go through what other types of requests are necessary for you.__
|
|
|
|
__GET /products/:id → Returns a single product identified by its id__
|
|
|
|
__GET /products →Returns all the products__
|
|
|
|
__POST /products → Creates a new product__
|
|
|
|
__DELETE /products/:id → Removes a single product identified by its id__
|
|
|
|
__PUT /products/:id → Updates an existing product__
|
|
|
|
Basic RESTFUL functionality is often referenced as a CRUD (Create, Read, Update, Delete).
|
|
|
|
# Handling request content
|
|
|
|
__To properly handle some requests, e.g., POST requests, in Express, a body-parser middleware must be taken into use. It's as simple as:__
|
|
|
|
const server = express()
|
|
|
|
server.use(express.json())
|
|
|
|
You might encounter a separate body-parser being used. This is no longer necessary, as Express 4.16+ includes a body parser middleware built-in.
|
|
|
|
express.urlencoded() __allows us to parse url-encoded forms by attaching the data to the request body.__
|
|
|
|
const app = express()
|
|
|
|
app.use(express.json())
|
|
|
|
app.use(express.urlencoded({extended: false}))
|
|
|
|
__The "extended" parameter's false value only says that we are not dealing with any complicated objects (objects with sub objects, etc)_
|
|
|
|
# Basic POST request
|
|
|
|
const app = express()
|
|
|
|
app.use(express.json())
|
|
|
|
app.use(express.urlencoded({extended: false}))
|
|
|
|
app.get("/", (req, res) => {
|
|
|
|
console.log("GET request init!");
|
|
|
|
res.sendFile(\_\_dirname + "/index.html");
|
|
|
|
})
|
|
|
|
app.post("/", (req, res) => {
|
|
|
|
console.log("POST request init!");
|
|
|
|
console.log(req.body);
|
|
|
|
res.redirect("/");
|
|
|
|
})
|
|
|
|
# Request Body
|
|
|
|
__GET__ __ methods should not in general have body parameters. The GET method should only be used to get data, the request should not convey data. __
|
|
|
|
__Despite this, most modern implementations can send and receive request bodies in all request types.__
|
|
|
|
__The requests that should not include body are __ __CONNECT__ __, __ __GET__ __, __ __HEAD__ __, __ __OPTIONS__ __, and __ __TRACE__ __. __
|
|
|
|
# Request Body Types
|
|
|
|
__Request body is parsed by the express body parser, and therefore it can contain any basic JavaScript data types. Notice that this differs from the request parameters and queries.__
|
|
|
|
__This also means that you do not know what the data type of any given body parameter will be. It might be necessary to check this in some cases.__
|
|
|
|
# TypeScript Body Casting
|
|
|
|
__If you want to enforce type safety, the solution is to validate the user input before using it.__
|
|
|
|
__This is usually a good idea even if not using TypeScript, since it is important to know what type all the variables are in your code.__
|
|
|
|
import express, { NextFunction, Request, Response} from 'express'
|
|
|
|
const server = express()
|
|
|
|
server.use(express.json())
|
|
|
|
interface Body {
|
|
|
|
name: string
|
|
|
|
age: number
|
|
|
|
}
|
|
|
|
const validate = (req: Request, res: Response, next: NextFunction ) => {
|
|
|
|
const { age, name } = req.body
|
|
|
|
if (typeof(age) !== 'number' || typeof(name) !== 'string') {
|
|
|
|
return res.status(400).send('Missing or invalid parameters')
|
|
|
|
}
|
|
|
|
next()
|
|
|
|
}
|
|
|
|
server.post('/', validate, (req: Request, res: Response) => {
|
|
|
|
const body: Body = req.body
|
|
|
|
console.log(body)
|
|
|
|
res.send(body.name.toUpperCase())
|
|
|
|
})
|
|
|
|
server.listen(3000)
|
|
|
|
# Exercise 7: Body Logging
|
|
|
|
Enable body parsing in your application.
|
|
|
|
Modify your logger middleware so that in addition to existing functionality, it also logs the request body if it exists.
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# Exercise 8: POST Requests
|
|
|
|
Add two more endpoints to your app:
|
|
|
|
POST /student should expect the request body to include student _id_ , _name _ and _email_ . If some of these parameters are missing, the endpoint should return a response with status code 400 and an error message. The endpoint should store the student information in an array called _students_ . The endpoint should return an empty response with status code 201.
|
|
|
|
GET /student/:id should return the information of a single student, identified by the _id_ request parameter. The response should be in JSON format. If there is no such student, the endpoint should return 404.
|
|
|
|
Modify the GET /students endpoint to return the list of all student _ids_ , without names or emails.
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|
|
# Exercise 9: PUT and DELETE
|
|
|
|
Add two more endpoints to your app:
|
|
|
|
PUT /student/:id should expect the request body to include student _name_ or _email_ . If both are missing, the endpoint should return a response with status code 400 and an error message. The endpoint should update an existing student identified by the request parameter _id_ .
|
|
|
|
DELETE /student/:id should remove a student identified by the request parameter _id_ .
|
|
|
|
Both endpoints should return 404 if there is no student matching the request parameter _id_ . On success both endpoint should return an empty response with status code 204.
|
|
|
|
---
|
|
|
|
Solution: https://gitlab.com/bctjs/week3-day2/-/blob/master/examples/day2/countdown.js
|
|
|