„Express“ paaiškinta pavyzdžiais - diegimas, maršruto parinkimas, tarpinės programos ir dar daugiau

Išreikšti

Kai reikia kurti žiniatinklio programas naudojant „Node.js“, serverio sukūrimas gali užtrukti daug laiko. Per daugelį metų „Node.js“ pakankamai subrendo dėl bendruomenės palaikymo. Naudojant „Node.js“ kaip žiniatinklio programų ir svetainių pagrindą, kūrėjai gali greitai pradėti dirbti su savo programa ar produktu.

Šiame vadovėlyje mes ketiname išnagrinėti „Express“, kuris yra „Node.js“ pagrindas žiniatinklio plėtrai, kuriame pateikiamos tokios funkcijos kaip maršruto parinkimas ir pateikimas bei palaikymas REST API.

Kas yra „Express“?

„Express“ yra populiariausia „Node.js“ sistema, nes norint paleisti programą ar API reikia minimalios sąrankos, ji yra greita ir tuo pačiu metu nepaliesta. Kitaip tariant, ji neįgyvendina savo filosofijos, kad programa arba API turėtų būti kuriama specialiai, skirtingai nei „Rails“ ir „Django“. Jo lankstumą galima apskaičiuoti pagal turimų npmmodulių skaičių, todėl jis tuo pačiu metu gali būti prijungiamas. Jei turite pagrindinių žinių apie HTML, CSS ir „JavaScript“ bei kaip apskritai veikia „Node.js“, per trumpą laiką negalėsite pradėti naudotis „Express“.

„Express“ sukūrė TJ Holowaychukas, o dabar jį prižiūri „Node.js“ fondas ir atvirojo kodo kūrėjai. Norėdami pradėti kurti „Express“, turite įdiegti „Node.js“ ir „npm“. Galite įdiegti „Node.js“ savo vietiniame kompiuteryje ir kartu su juo ateina komandinės eilutės įrankis npm, kuris padės mums įdiegti papildinius arba vadinamąsias priklausomybes vėliau mūsų projekte.

Norėdami patikrinti, ar viskas tinkamai įdiegta, atidarykite terminalą ir įveskite:

node --version v5.0.0 npm --version 3.5.2

Jei gaunate versijos numerį, o ne klaidą, tai reiškia, kad sėkmingai įdiegėte „Node.js“ ir „npm“.

Kodėl verta naudoti Express?

Prieš pradėdami naudoti „Express“ kaip „backend“ sistemos mechanizmą, pirmiausia išnagrinėkime, kodėl turėtume jį apsvarstyti, ar jo populiarumo priežastis.

  • „Express“ leidžia kurti vieno puslapio, kelių puslapių ir hibridines interneto ir mobiliąsias programas. Kitas įprastas backend naudojimas yra suteikti klientui API (tiek žiniatinklio, tiek mobiliojo ryšio).
  • Jame yra numatytasis šablonų variklis „Jade“, kuris padeda palengvinti duomenų srautą į svetainės struktūrą ir palaiko kitus šablonų variklius.
  • Jis palaiko „MVC“ („Model-View-Controller“) - labai įprastą žiniatinklio programų projektavimo architektūrą.
  • Tai yra daugiaplatformis ir neapsiriboja jokia konkreti operacine sistema.
  • Jis naudoja „Node.js“ vieno sriegio ir asinchroninį modelį.

Kai mes kuriame projektą naudodami npm, mūsų projektas turi turėti package.jsonfailą.

Kuriamas „package.json“

JSON („JavaScript Object Notation“) faile yra visa informacija apie bet kurį „Express“ projektą. Įdiegtų modulių skaičius, projekto pavadinimas, versija ir kita meta informacija. Norėdami pridėti „Express“ kaip modulį į savo projektą, pirmiausia turime sukurti projekto katalogą, tada sukurti failą „package.json“.

mkdir express-app-example cd express-app-example npm init --yes

Tai sugeneruos package.jsonfailą projekto katalogo šaknyje. Norint įdiegti bet kurį modulį iš npmmūsų package.json, tame kataloge turi būti failas.

{ "name": "express-web-app", "version": "0.1.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "license": "MIT" }

„Express“ diegimas

Dabar mes turime package.jsonfailą, mes galime įdiegti „Express“ paleidę komandą:

npm install --save express

Galime patvirtinti, kad „Express“ teisingai įdiegta dviem būdais. Pirma, package.jsonfaile bus naujas skyrius, pavadintas dependenciespagal kurį egzistuoja mūsų „Express“:

{ "name": "express-web-app", "version": "0.1.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "license": "MIT", "dependencies": { "express": "4.16.0" } }

Antrasis būdas yra tas node_modules, kad mūsų katalogo šaknyje netikėtai atsirado naujas aplankas, vadinamas . Šiame aplanke saugomi paketai, kuriuos vietoje įdiegiame savo projekte.

Serverio kūrimas naudojant „Express“

Norėdami naudoti įdiegtą „Express“ paketą ir sukurti paprastą serverio programą, sukursime failą „“, index.jsesantį mūsų projekto kataloge.

const express = require('express'); const app = express(); app.get('/', (req, res) => res.send('Hello World!')); app.listen(3000, () => console.log('Example app listening on port 3000!'));

Norėdami paleisti serverį, eikite į terminalą ir įveskite:

node index.js

Tai paleis serverį. Ši minimali programa klausysis 3000 prievado. Mes pateikiame užklausą per savo naršyklę //localhost:3000ir mūsų serveris atsakys, su Hello Worldkuria naršykle yra klientas, ir ten bus rodomas pranešimas.

Pirmoji mūsų kodo eilutė naudoja requirefunkciją expressmoduliui įtraukti . Taip mes įtraukiame ir naudojame paketą, įdiegtą nuo npm, į bet kurį „JavaScript“ failą savo projekte. Prieš pradėdami naudoti „Express“, turime apibrėžti jo egzempliorių, kuris tvarko užklausą ir atsakymą iš serverio klientui. Mūsų atveju tai yra kintamasis app.

app.get()yra funkcija, nurodanti serveriui, ką daryti, kai getiškviečiama užklausa nurodytu maršrutu. Jis turi atgalinio skambinimo funkciją, (req, res)kuri klauso gaunamo užklausos reqobjekto ir atitinkamai reaguoja naudodama resatsakymo objektą. Abi reqir resyra prieinamos mums „Express“ sistemoje.

reqObjektas reiškia prašymą HTTP ir turi savybes prašymo užklausos eilutę, parametrai, kūno, ir HTTP antraštes. Res objektas reiškia HTTP atsakymą, kurį „Express“ programa siunčia, kai gauna HTTP užklausą. Mūsų atveju mes siunčiame tekstą, Hello Worldkai tik pateikiama užklausa dėl maršruto /.

Galiausiai, app.listen()yra funkcija, paleidžianti prievadą ir pagrindinį kompiuterį, mūsų atveju localhost- ryšiams, norintiems išklausyti gaunamas kliento užklausas. Mes galime apibrėžti uosto numerį, pvz 3000.

„Express“ programos anatomija

Įprastoje „Express“ serverio failo struktūroje greičiausiai bus šios dalys:

Priklausomybės

Importuoti priklausomybes, tokias kaip „express“. Šios priklausomybės įdiegiamos taip, npmkaip tai darėme ankstesniame pavyzdyje.

Instantiations

These are the statements to create an object. To use express, we have to instantiate the app variable from it.

Configurations

These statements are the custom application based settings that are defined after the instantiations or defined in a separate file (more on this when discuss the project structure) and required in our main server file.

Middleware

These functions determine the flow of request-response cycle. They are executred after every incoming request. We can also define custom middleware functions. We have section on them below.

Routes

They are the endpoints defined in our server that helps to perform operations for a particular client request.

Bootstrapping Server

The last that gets executed in an Express server is the app.listen() function which starts our server.

We will now start disussing sections that we haven’t previously discussed about.

Routing

Routing refers to how an server side application responds to a client request to a particular endpoint. This endpoint consists of a URI (a path such as / or /books) and an HTTP method such as GET, POST, PUT, DELETE, etc.

Routes can be either good old web pages or REST API endpoints. In both cases the syntax is similar syntax for a route can be defined as:

app.METHOD(PATH, HANDLER);

Routers are helpful in separating concerns such as different endpoints and keep relevant portions of the source code together. They help in building maintainable code. All routes are defined before the function call of app.listen(). In a typical Express application, app.listen() will be last function to execute.

Routing Methods

HTTP is a standard protocol for a client and a server to communicate over. It provides different methods for a client to make request. Each route has at least on hanlder function or a callback. This callback function determines what will be the response from server for that particular route. For example, a route of app.get() is used to handle GET requests and in return send simple message as a response.

// GET method route app.get('/', (req, res) => res.send('Hello World!'));

Routing Paths

A routing path is a combination of a request method to define the endpoints at which requests can be made by a client. Route paths can be strings, string patterns, or regular expressions.

Let us define two more endpoints in our server based application.

app.get('/home', (req, res) => { res.send('Home Page'); }); app.get('/about', (req, res) => { res.send('About'); });

Consider the above code as a bare minimum website which has two endpoints, /home and /about. If a client makes a request for home page, it will only response with Home Page and on /about it will send the response: About Page. We are using the res.send function to send the string back to the client if any one of the two routes defined is selected.

Routing Parameters

Route parameters are named URL segments that are used to capture the values specified at their position in the URL. req.params object is used in this case because it has access to all the parameters passed in the url.

app.get('/books/:bookId', (req, res) => { res.send(req.params); });

The request URL from client in above source code will be //localhost:3000/books/23. The name of route parameters must be made up of characters ([A-Za-z0-9_]). A very general use case of a routing parameter in our application is to have 404 route.

// For invalid routes app.get('*', (req, res) => { res.send('404! This is an invalid URL.'); });

If we now start the server from command line using node index.js and try visiting the URL: //localhost:3000/abcd. In response, we will get the 404 message.

Middleware Functions

Middleware functions are those functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle. The objective of these functions is to modify request and response objects for tasks like parsing request bodies, adding response headers, make other changes to request-response cycle, end the request-response cycle and call the next middleware function.

The next function is a function in the Express router which is used to execute the other middleware functions succeeding the current middleware. If a middleware function does include next() that means the request-response cycle is ended there. The name of the function next() here is totally arbitary and you can name it whatever you like but is important to stick to best practices and try to follow a few conventions, especially if you are working with other developers.

Also, when writing a custom middleware do not forget to add next() function to it. If you do not mention next() the request-response cycle will hang in middle of nowhere and you servr might cause the client to time out.

Let use create a custom middleware function to grasp the understanding of this concept. Take this code for example:

const express = require('express'); const app = express(); // Simple request time logger app.use((req, res, next) => { console.log("A new request received at " + Date.now()); // This function call tells that more processing is // required for the current request and is in the next middleware function/route handler. next(); }); app.get('/home', (req, res) => { res.send('Home Page'); }); app.get('/about', (req, res) => { res.send('About Page'); }); app.listen(3000, () => console.log('Example app listening on port 3000!'));

To setup any middleware, whether a custom or available as an npm module, we use app.use() function. It as one optional parameter path and one mandatory parameter callback. In our case, we are not using the optional paramaeter path.

app.use((req, res, next) => { console.log('A new request received at ' + Date.now()); next(); });

The above middleware function is called for every request made by the client. When running the server you will notice, for the every browser request on the endpoint /, you will be prompt with a message in your terminal:

A new request received at 1467267512545

Middleware functions can be used for a specific route. See the example below:

const express = require('express'); const app = express(); //Simple request time logger for a specific route app.use('/home', (req, res, next) => { console.log('A new request received at ' + Date.now()); next(); }); app.get('/home', (req, res) => { res.send('Home Page'); }); app.get('/about', (req, res) => { res.send('About Page'); }); app.listen(3000, () => console.log('Example app listening on port 3000!'));

This time, you will only see a similar prompt when the client request the endpoint /home since the route is mentioned in app.use(). Nothing will be shown in the terminal when the client requests endpoint /about.

Order of middleware functions is important since they define when to call which middleware function. In our above example, if we define the route app.get('/home')... before the middleware app.use('/home')..., the middleware function will not be invoked.

Third Party Middleware Functions

Middleware functions are useful pattern that allows developers to reuse code within their applications and even share it with others in the form of NPM modules. The essential definition of middleware is a function with three arguments: request (or req), response (res), and next which we observer in the previous section.

Often in our Express based server application, we will be using third party middleware functions. These functions are provided by Express itself. They are like plugins that can be installed using npm and this is why Express is flexible.

Some of the most commonly used middleware functions in an Express appication are:

bodyParser

It allows developers to process incoming data, such as body payload. The payload is just the data we are receiving from the client to be processed on. Most useful with POST methods. It is installed using:

npm install --save body-parser

Usage:

const bodyParser = require('body-parser'); // To parse URL encoded data app.use(bodyParser.urlencoded({ extended: false })); // To parse json data app.use(bodyParser.json());

It is probably one of the most used third-party middleware function in any Express applicaiton.

cookieParser

It parses Cookie header and populate req.cookies with an object keyed by cookie names. To install it,

$ npm install --save cookie-parser
const cookieParser = require('cookie-parser'); app.use(cookieParser());

session

This middleware function creates a session middleware with given options. A session is often used in applications such as login/signup.

$ npm install --save session
app.use( session({ secret: 'arbitary-string', resave: false, saveUninitialized: true, cookie: { secure: true } }) );

morgan

The morgan middleware keeps track of all the requests and other important information depending on the output format specified.

npm install --save morgan
const logger = require('morgan'); // ... Configurations app.use(logger('common'));

common is a predefined format case which you can use in the application. There are other predefined formats such as tiny and dev, but you can define you own custom format too using the string parameters that are available to us by morgan.

A list of most used middleware functions is available at this link.

Serving Static Files

To serve static files such as CSS stylesheets, images, etc. Express provides a built in middleware function express.static. Static files are those files that a client downloads from a server.

It is the only middleware function that comes with Express framework and we can use it directly in our application. All other middlewares are third party.

By default, Express does not allow to serve static files. We have to use this middleware function. A common practice in the development of a web application is to store all static files under the ‘public’ directory in the root of a project. We can serve this folder to serve static files include by writing in our index.js file:

app.use(express.static('public'));

Now, the static files in our public directory will be loaded.

//localhost:3000/css/style.css //localhost:3000/images/logo.png //localhost:3000/images/bg.png //localhost:3000/index.html

Multiple Static Directories

To use multiple static assets directories, call the express.static middleware function multiple times:

app.use(express.static('public')); app.use(express.static('files'));

Virtual Path Prefix

A fix path prefix can also be provided as the first argument to the express.static middleware function. This is known as a Virtual Path Prefix since the actual path does not exist in project.

app.use('/static', express.static('public'));

If we now try to load the files:

//localhost:3000/static/css/style.css //localhost:3000/static/images/logo.png //localhost:3000/static/images/bg.png //localhost:3000/static/index.html

This technique comes in handy when providing multiple directories to serve static files. The prefixes are used to help distinguish between the multiple directories.

Template Engines

Template engines are libraries that allow us to use different template languages. A template language is a special set of instructions (syntax and control structures) that instructs the engine how to process data. Using a template engine is easy with Express. The popular template engines such as Pug, EJS, Swig, and Handlebars are compatible with Express. However, Express comes with a default template engine, Jade, which is the first released version of Pug.

To demonstrate how to use a Template Engine, we will be using Pug. It is a powerful template engine that provide features such as filters, includes, interpolation, etc. To use it, we have to first install as a module in our project using npm.

npm install --save pug

This command will install the pug and to verify that installed correctly, just take a look at the package.json file. To use it with our application first we have to set it as the template engine and create a new directory ‘./views’ where we will store all the files related to our template engine.

app.set('view engine', 'pug'); app.set('views', './views');

Since we are using app.set() which indicates configuration within our server file, we must place them before we define any route or a middleware function.

In the views direcotry, create file called index.pug.

doctype html html head tite="Hello from Pug" body p.greetings Hello World! 

To run this page, we will add the following route to our application.

app.get('/hello', (req, res) => { res.render('index'); });

Since we have already set Pug as our template engine, in res.render we do not have to provide .pug extension. This function renders the code in any .pug file to HTML for the client to display. The browsers can only render HTML files. If you start the server now, and visit the route //localhost:3000/hello you will see the output Hello World rendered correctly.

In Pug, you must notice that we do not have to write closing tags to elements as we do in HTML. The above code will be rendered into HTML as:

   Hello from Pug   

Hello World!

The advantage of using a Template Engine over raw HTML files is that they provide support for performing tasks over data. HTML cannot render data directly. Frameworks like Angular and React share this behaviour with template engines.

You can also pass values to template engine directly from the route handler function.

app.get('/', (req, res) => { res.render('index', { title: 'Hello from Pug', message: 'Hello World!' }); });

For above case, our index.pug file will be written as:

doctype html html head title= title body h1= message

The output will be the same as previous case.

Project Structure of an Express App

Since Express does not enforces much on the developer using it, sometimes it can get a bit overwhelming to what project structure one should follow. It does not has a defined structure officially but most common use case that any Node.js based application follows is to separate different tasks in different modules. This means to have separate JavaScript files.

Let us go through a typical strucutre of an Express based web application.

project-root/ node_modules/ // This is where the packages installed are stored config/ db.js // Database connection and configuration credentials.js // Passwords/API keys for external services used by your app config.js // Environment variables models/ // For mongoose schemas books.js things.js routes/ // All routes for different entities in different files books.js things.js views/ index.pug 404.pug ... public/ // All static files images/ css/ javascript/ app.js routes.js // Require all routes in this and then require this file in app.js package.json

Tai yra modelis, paprastai žinomas kaip MVC, modelio vaizdo valdiklis. Tiesiog todėl, kad mūsų duomenų bazės modelis, programos vartotojo sąsaja ir valdikliai (mūsų atveju - maršrutai) yra parašyti ir saugomi atskiruose failuose. Šis dizaino modelis palengvina bet kurios žiniatinklio programos mastelį, jei ateityje norite pristatyti daugiau maršrutų ar statinių failų, o kodas yra prižiūrimas.