Npm

Express task

Run this task with the command.


Configure one or more servers for grunt to start, the minimal config would be:

grunt.initConfig({    express{      default_option{}}});grunt.loadNpmTasks('grunt-express');grunt.registerTask('default','express');

Start your express server (or restart a server if it is already started).

Note that when is false, this server only runs as long as grunt is running. Once grunt’s tasks have completed, the web server stops. This behavior can be changed by appending a task at the end of your task list like so

grunt.registerTask('myServer','express','express-keepalive');

Now when you run , your express server will be kept alive until you manually terminate it.

Such feature can also be enabled ad-hoc by running the command like .

Usage

Basic usage using an HS256 secret:

var jwt =require('express-jwt');app.get('/protected',jwt({ secret'shhhhhhared-secret'}),function(req,res){if(!req.user.admin)returnres.sendStatus(401);res.sendStatus(200);});

You can specify audience and/or issuer as well:

jwt({  secret'shhhhhhared-secret',  audience'http://myapi/protected',  issuer'http://issuer'})

If you are using a base64 URL-encoded secret, pass a with encoding as the secret instead of a string:

jwt({ secretnewBuffer('shhhhhhared-secret','base64')})

Optionally you can make some paths unprotected as follows:

app.use(jwt({ secret'shhhhhhared-secret'}).unless({path'/token'}));

This is especially useful when applying to multiple routes. In the example above, can be a string, a regexp, or an array of any of those.

This module also support tokens signed with public/private key pairs. Instead of a secret, you can specify a Buffer with the public key

var publicKey =fs.readFileSync('/path/to/public.pub');jwt({ secret publicKey });

By default, the decoded token is attached to but can be configured with the option.

jwt({ secret publicKey, requestProperty'auth'});

The token can also be attached to the object with the option. This option will override any .

jwt({ secret publicKey, resultProperty'locals.user'});

A custom function for extracting the token from a request can be specified with the option. This is useful if you need to pass the token through a query parameter or a cookie. You can throw an error in this function and it will be handled by .

app.use(jwt({  secret'hello world !',  credentialsRequiredfalse,getTokenfunctionfromHeaderOrQuerystring(req){if(req.headers.authorization&&req.headers.authorization.split('')==='Bearer'){returnreq.headers.authorization.split('')1;}elseif(req.query&&req.query.token){returnreq.query.token;}returnnull;}}));

If you are developing an application in which the secret used to sign tokens is not static, you can provide a callback function as the parameter. The function has the signature: :

  • () — The express object.
  • () — An object with the JWT claims.
  • () — A function with signature to be invoked when the secret is retrieved.
    • () — The error that occurred.
    • () — The secret to use to verify the JWT.
var jwt =require('express-jwt');var data =require('./data');var utilities =require('./utilities');varsecretCallback=function(req,payload,done){var issuer =payload.iss;data.getTenantByIdentifier(issuer,function(err,tenant){if(err){returndone(err);}if(!tenant){returndone(newError('missing_secret'));}var secret =utilities.decrypt(tenant.secret);done(null, secret);});};app.get('/protected',jwt({ secret secretCallback }),function(req,res){if(!req.user.admin)returnres.sendStatus(401);res.sendStatus(200);});

It is possible that some tokens will need to be revoked so they cannot be used any longer. You can provide a function as the option. The signature of the function is :

  • () — The express object.
  • () — An object with the JWT claims.
  • () — A function with signature to be invoked once the check to see if the token is revoked or not is complete.
    • () — The error that occurred.
    • () — if the JWT is revoked, otherwise.

For example, if the claim pair is used to identify a JWT:

var jwt =require('express-jwt');var data =require('./data');var utilities =require('./utilities');varisRevokedCallback=function(req,payload,done){var issuer =payload.iss;var tokenId =payload.jti;data.getRevokedToken(issuer, tokenId,function(err,token){if(err){returndone(err);}returndone(null,!!token);});};app.get('/protected',jwt({    secret'shhhhhhared-secret',    isRevoked isRevokedCallback}),function(req,res){if(!req.user.admin)returnres.sendStatus(401);res.sendStatus(200);});

The default behavior is to throw an error when the token is invalid, so you can add your custom logic to manage unauthorized access as follows:

app.use(function(err,req,res,next){if(err.name==='UnauthorizedError'){res.status(401).send('invalid token...');}});

You might want to use this module to identify registered users while still providing access to unregistered users. You can do this by using the option :

app.use(jwt({  secret'hello world !',  credentialsRequiredfalse}));

Языковые конструкции, специфичные для TS

Есть несколько языковых конструкций в TS, которые отсутствуют в JS стандарте. Например Enum. Он существует в 2х вариантах — enum и const enum.

Const enum позволяет задать типизированные имена для числовых значений, например:

Стандартный enum дает больше возможностей:

Это достигается тем, что стандартный TS enum компилируется в конструкцию вида:

С одной стороны это может быть удобно, но при этом ваш код становится не совместимым с JS, разработчику без знания TS будет сложнее с этим разобраться, могут быть проблемы с отладкой.

Лучше избегать таких конструкций при минималистическом использовании TS.

Build App

Я разрабатываю CLI билд систему build-app, которая заточена под разработку full-stack JS приложений с back-end на ноде и SPA клиенте на одном из современных клиентских фреймворков (React/Vue/Angular2). Помимо самой билд системы в комплекте идет набор темплейтов от простого «Hello World» приложения, до реализации базовой функциональности простого веб сайта, с сохранением данных (SQL/NoSql), авторизацией, логированием отправкой имейлов и т.д.

Большинство темплейтов используют TS для серверной части и могут использоваться как пример описанного в статье подхода.


Кроме того в build-app помогает с настройкой окружения для TS: при создании нового проекта можно сгенерировать настройки для IDE (VS Code или WebStorm), есть опция запуска проекта в режиме watch — при изменении кода проект перезапускаться и есть встроенный скрипт сборки проекта для продакшина.

Подробнее об самой билд системе в ридми проекта.

Исходники кода темпелйтов можно посмотреть без использования build-app непосредственно в их репозиториях: Simple Template и Full template (mongo).

Буду рад вашим замечаниям.

Template Engines

Template engines that are Express compliant out of the box.

  • Pug — Haml inspired template engine (formerly Jade)
  • Haml.js — Haml implementation
  • EJS — Embedded JavaScript template engine
  • hbs — adapter for Handlebars.js, an extension of Mustache.js template engine
  • React — renders React components on server. It renders static markup and does not support mounting those views on the client.
  • h4e — adapter for Hogan.js, with support for partials and layouts
  • hulk-hogan — adapter for Twitter’s Hogan.js (Mustache syntax), with support for Partials
  • combyne.js — A template engine that hopefully works the way you’d expect.
  • swig — fast, Django-like template engine
  • Nunjucks — inspired by jinja/twig
  • marko — A fast and lightweight HTML-based templating engine that compiles templates to CommonJS modules and supports streaming, async rendering and custom tags. (render directly to the HTTP response stream)
  • whiskers — small, fast, mustachioed
  • Blade — HTML Template Compiler, inspired by Jade & Haml
  • Haml-Coffee — Haml templates where you can write inline CoffeeScript.
  • Webfiller — plain-html5 dual-side rendering, self-configuring routes, organized source tree, 100% js.
  • express-handlebars — A Handlebars view engine for Express which doesn’t suck.
  • express-views-dom — A DOM view engine for Express.
  • rivets-server — Render Rivets.js templates on the server.
  • Exbars — A flexible Handlebars view engine for Express
  • Liquidjs — A Liquid engine implementation for both Node.js and browsers
  • express-tl — A template-literal engine implementation for Express.
  • vuexpress — A Vue.js server side rendering engine for Express.js.

The Consolidate.js library unifies the APIs of these template engines to a single Express-compatible API.

Release History

  • 2014-05-09 #58
  • 2014-05-04 bump npm dependencies
  • 2014-04-30 fixes server reload
  • 2013-07-16 use grunt-contrib-watch, support both serverreload and livereload
  • 2013-04-25 use forever-monitor npm v1.2.1
  • 2013-03-24 fixed npm v1.2.15 compatibility issue
  • 2013-03-14 support ‘debug-brk’ option for launching server in child process (so it can be linked to a remote debugger); also point forever-monitor dependency to its github verion (has fix for accepting ‘debug-brk’ options)
  • 2013-03-13 do not defalt hostname to «localhost» when none is provided as that will prevent access to the server through IP addres
  • 2013-03-11 Make static directories not browsable as it breaks twitter bootstrap (suggested by @hmalphettes)
  • 2013-02-28 Switch to use forever-monitor (instead of node-supervisor). Removed «keepalive» option, instead enable the feature using «express-keepalive» task.
  • 2013-02-25 Fixes #1, changing option «watchChanges» to «supervisor».
  • 2013-02-24 Added missing «connect» dependency, factored out some logic to util.js.
  • 2013-02-23 first draft.

grunt-express v1.0

v1.0 is nearly a complete re-done, it acts as a higher-level grunt task that depends on (and consumes) . It will dynamically configure tasks based on your task setup at runtime, and it will run if necessary. Here’s the list of high level changes

  1. use to manage reloading express server, instead of
  2. support both and (pre-v1.0 users: will no longer manage to restart your server by default, you would have to set to to regain the old behavior)
  3. if is set to in , then the following are true:
    • server will be started in the same process as your (so developers can run debugger using Webstorm or other tools)
    • server will be run WITHOUT the call (you can optionally append the task to keep the server running), this allows you to run tests using grunt-express
  4. continue to support + use cases
  5. discontinue support of

Introduction

Now that you know why JWTs are all the rage, wouldn’t it be nice if you could start using them in your Express app? JWTs are perfect to use in REST APIs, but they can also be used in everyday web applications to leverage session data while keeping the server stateless. This is accomplished by storing the JWT in a cookie.

Wait a minute, aren’t we using JWTs to avoid using cookies? The answer to that question is: not quite. The way cookies have been used in the past is we store a session ID in them, and on subsequent requests, read that ID back. Then with that ID, we use that to look up session data for the user from the file system or from a database. But since the JWT contains all the information we need, it saves us the file system / DB look up, thus keeping the server stateless.

TypeScript как транспайлер

Транспалировать или нет? Большинство новых проектов использует ES6, это очень большое дополнение JS языка, которое действительно имеет значение для комфорта и эффективности разработки. В последней LTS версии нода ES6 почти полностью поддерживается и может использоваться без дополнительной транспаляции. Если вы еще не знакомы с ES6 существует огромное количество вводных статей, например об этом можно почитать тут.

Кроме ES6 одной из основных новых фич языка является async/await, которая поддерживается в TS. Это новый подход к работе с асинхронным кодом, следующий шаг после колбэков и промисов. Подробнее о преимуществах здесь. Поддержка async/await уже есть в ноде, но пока не в LTS версии. При написании нового проекта имеет смысл сразу использовать async/awiat. Во-первых, это гораздо удобнее, во-вторых, для преобразования кода из промисов в async/awiat в дальнейшем потребуются дополнительные усилия и лучше это сделать сразу.

Помимо этого TS поддерживает импортирование в ES6 стиле (import… from вместо require), поддержка в ноде для этого будет еще не скоро, т.к. есть ряд особенностей подробнее здесь

Но в TS это можно использовать уже сейчас и хотя это не будет 100% реализацией спецификации, в подавляющем количестве случаев это не важно

Для того чтобы легко отлаживать код локально рекомендуется на машине разработчика компилировать код в ES6, т.е. локально вам нужна версия нода 6.x, при этом в продакшине может использоваться нод более старых версий, тогда при компиляции для продакшина надо дополнительно компилировать из ES6 в ES5, сразу через TS или с использованием Babel.

Boilerplate

  • Boilerplate — boilerplate app supplying Express, Connect, Socket-IO, Jade/Pug and more.
  • express-mongoose-es6-rest-api — A boilerplate application for building REST APIs using express and mongoose in ES6 with code coverage.
  • express-site-template — jade, stylus, sessions with redis
  • backbone-express-mongoose-socketio — Boilerplate app supplying backbone, mongoose, and socket.io
  • bearcat-todo — a simple todo app built on bearcat and express, bearcat makes it easy write simple, maintainable node.js
  • node-scaffold — a beautiful scaffolding module. It generates an app (MVC) based on express and mongoose for a given json configuration.

Custom Mongoose Plugins

The app also contains 2 custom mongoose plugins that you can attach to any mongoose model schema. You can find the plugins in .

constmongoose=require('mongoose');const{toJSON,paginate}=require('./plugins');constuserSchema=mongoose.Schema({},{ timestampstrue});userSchema.plugin(toJSON);userSchema.plugin(paginate);constUser=mongoose.model('User', userSchema);

The toJSON plugin applies the following changes in the toJSON transform call:

  • removes __v, createdAt, updatedAt, and any schema path that has private: true
  • replaces _id with id

The paginate plugin adds the static method to the mongoose schema.

Adding this plugin to the model schema will allow you to do the following:

constqueryUsers=async(filter,options)=>{constusers=awaitUser.paginate(filter, options);return users;};

The param is a regular mongo filter.

The param can have the following (optional) fields:

constoptions={  sortBy'name:desc',  limit5,  page2,};

The method returns a Promise, which fulfills with an object having the following properties:

{"results","page"2,"limit"5,"totalPages"10,"totalResults"48}

Тестирование Express

Последнее обновление: 17.01.2017

Вначале установим supertest с помощью команды:

npm install supertest --save-dev

Первым делом определим простенький файл приложения app.js:

const express = require("express");
var app = express();

app.get("/", function (request, response){
	
	response.send("Hello Test");
});

app.listen(3000);

module.exports.app = app;

Данное приложение при обращении по главному маршруту «/» отправляет в ответ некоторую строку «Hello Test».

И чтобы задействовать данное приложение в тесте, оно оформляется в виде модуля:

Далее для тестов создадим в каталоге проекта новый файл app.test.js:

const request = require("supertest");

var app = require("./app").app;

it("should return Hello Test", function(done){
	
	request(app)
		.get("/")
		.expect("Hello Test")
		.end(done);
});

Для тестирования получаем модули supertest и нашего приложения и используем метод для получения результата.

Для настройки и выполнения теста в request передается функционал приложения:

request(app)

устанавливаем маршрут, по которому будем обращаться в приложении:

get("/")

Устанавливаем ожидаемый результат через метод :

expect("Hello Test")

и с помощью метода выполняем тест:

end(done)

Для запуска этого теста у нас опять же должна быть настроена должным образом команда в файле package.json:

{
  "name": "testapp",
  "version": "1.0.0",
  "scripts": {
    "test": "mocha *.test.js"
  },
  "devDependencies": {
    "mocha": "^3.2.0",
    "supertest": "^2.0.1"
  },
  "dependencies": {
    "express": "^4.14.0"
  }
}

Запустим тест на выполнение:

Зеленый маркер в сообщении теста указывает, что тест пройден успешно, а приложение действительно возвращает строку «Hello Test».

Рассмотрим еще пару тестов. Для этого изменим файл app.js следующим образом:

const express = require("express");
var app = express();

app.get("/", function (request, response){
	
	response.send("Hello Test");
});

app.get("/error", function (request, response){
	
	response.status(404).send("NotFound");
});

app.get("/user", function (request, response){
	
	response.send({name:"Tom", age: 22});
});

app.listen(3000);

module.exports.app = app;

Здесь определена обработка для трех маршрутов. Для их тестирования изменим файл app.test.js:

const request = require("supertest");
const assert = require("assert");

var app = require("./app").app;

it("should return Hello Test", function(done){
	
	request(app)
		.get("/")
		.expect("Hello Test")
		.end(done);
});

it("should return NotFound with status 404", function(done){
	
	request(app)
		.get("/error")
		.expect(404)
		.expect("NotFound")
		.end(done);
});

it("should return user with name Tom and age 22", function(done){
	
	request(app)
		.get("/user")
		.expect(function(response){
			assert.deepEqual(response.body, {name:"Tom", age:22});
		})
		.end(done);
});

Если нам надо проверить статусный код, то также можем передать ожидаемый код статуса в метод

Если необходимо проверить какие-то комплексные объекты, которые отправляются в ответе клиенту, то в метод expect передается функция, в которую в качестве параметра передается объект ответа response. А через объект можно получить весь ответ и сравнить его с ожидаемым значением. Для сравнения комплексных объектов можно применить метод или библиотеки assert, рассмотренной в прошлой теме.

Запустим тесты и проверим результат:

НазадВперед

Authorization

The middleware can also be used to require certain rights/permissions to access a route.

constexpress=require('express');constauth=require('../../middlewares/auth');constuserController=require('../../controllers/user.controller');constrouter=express.Router();router.post('/users',auth('manageUsers'),userController.createUser);

In the example above, an authenticated user can access this route only if that user has the permission.

The permissions are role-based. You can view the permissions/rights of each role in the file.

If the user making the request does not have the required permissions to access this route, a Forbidden (403) error is thrown.

Usage

secret — can be either a function or a string. If it is a string, that will be used to sign / verify with. If it is a function, that function must return a string. The returned string will be used to sign / verify with. When the function is called, it will be called with the Request object () as the first parameter.

options — must be an object. These are the available options:

  • cookie: (string) The name of the cookie (default: )
  • cookieOptions: (object) Options to use when storing the cookie (default: )
  • cookies: (boolean) If true, will use cookies, otherwise will use the Authorization header (default: )
  • refresh: (boolean) Indicates if the JWT should be refreshed and stored every request (default: )
  • reqProperty: (string) The property of req to populate (default: )
  • revoke: (function) will call this function (default: )
  • signOptions: (object) Options to use when signing the JWT (default: )
  • stales: (number) Milliseconds when the jwt will go stale (default: (15 minutes))
  • verify: (function) Additional verification. Must return a boolean (default: )
  • verifyOptions: (object) Options to use when verifying the JWT (default: )

Returns a middleware function that ensures a JWT is valid and fresh. Useful to protect sensitive actions from CSRF. This method will trigger error handling if the JWT is not active.

If using cookies, this method will clear the current JWT out of the cookie.

secret — can be either a function or a string. If it is a string, that will be used to sign / verify with. If it is a function, that function must return a string. The returned string will be used to sign / verify with. When the function is called, it will be called with the payload object as the first parameter.

payload — the payload to use.

Returns a middleware function that requires the payload to contain / match certain data. This method will trigger error handling if the JWT fails the requirement.

key — This is they key used to look up the value in the payload. If only this value is passed to , then the middleware function will check that the value is truthy (value == true).

operator — If supplied, must be one of the following:

  • ==
  • ===
  • !=
  • !==
  • <
  • <=
  • >
  • >=

value — The value to compare the payload data against

Returns a middleware function that ensures a JWT is valid. This method will trigger error handling if the JWT is not valid.

Начало работы с Express

Последнее обновление: 18.11.2018

В этой главе мы рассмотрим создание сервера с помощью фреймворка Express. Казалось бы, зачем нам нужен дополнительный фреймворк, если мы можем воспользоваться готовым модулем http, который есть в Node.js API. Однако Express сам использует модуль http, но вместе с тем предоставляет ряд готовых абстракций, которые упрощают создание сервера и серверной логики, в частности, обработка отправленных форм, работа с куками, CORS и т.д.

Создадим для проекта новый каталог, который назовем, к примеру, expressapp. Для хранения информации обо всех зависимостях проекта определим в этом каталоге новый файл package.json:

{
  "name": "expressapp",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.16.4"
  }
}

Далее перейдем к этому каталогу в командной строке/терминале и для добавления всех нужных пакетов выполним команду:

npm install

Создадим в каталоге проекта новый файл app.js:

// подключение express
const express = require("express");
// создаем объект приложения
const app = express();
// определяем обработчик для маршрута "/"
app.get("/", function(request, response){
	
	// отправляем ответ
	response.send("<h2>Привет Express!</h2>");
});
// начинаем прослушивать подключения на 3000 порту
app.listen(3000);

Для использования Express в начале надо создать объект, который будет представлять приложение:

const app = express();

Для обработки запросов в Express определено ряд встроенных функций, и одной из таких является функция app.get(). Она обрабатывает GET-запросы протокола HTTP и позволяет связать маршруты с определенными обработчиками. Для этого первым параметром передается маршрут, а вторым — обработчик, который будет вызываться, если запрос к серверу соответствует данному маршруту:

app.get("/", function(request, response){
	
	// отправляем ответ
	response.send("<h2>Привет Express!</h2>");
});

Маршрут «/» представляет корневой маршрут.

Для запуска сервера вызывается метод , в который передается номер порта.

Запустим проект и обратимся в браузере по адресу http://localhost:3000/:

И что важно, Express опирается на систему маршрутов, поэтому все другие запросы, которые не соответствуют корневому маршруту «/», не будут обрабатываться:

Теперь изменим файл app.js:

const express = require("express");

const app = express();
app.get("/", function(request, response){
	
	response.send("<h1>Главная страница</h1>");
});
app.get("/about", function(request, response){
	
	response.send("<h1>О сайте</h1>");
});
app.get("/contact", function(request, response){
	
	response.send("<h1>Контакты</h1>");
});
app.listen(3000);

Теперь в приложении определено три маршрута, которые будут обрабатываться сервером:

НазадВперед

Типизация внешних библиотек

TS позволяет описывать JS библиотеки с помощью файлов деклараций (declaration files *.d.ts). При этом TS достаточно гибок и поддерживает различные типы библиотек и режимы их использования (глобальные библиотеки, модули, UMP и пр). Почти сразу после появления TS, появился oupensource репозиторий DefinitelyTyped, в котором средствами энтузиастов добавлялись и обновлялись файлы деклараций для множества различных JS библиотек.

Изначально разработчики TS не занимались вопросом менеджмента файлов деклараций и появились проекты tsd и typings, которые позволяли устанавливать эти файлы похожим образом на установку npm пакетов. Начиная с TypeScript 2.0 можно использовать npm для загрузки файлов деклараций. Подробнее здесь.

Для разработки на ноде с TS вам как минимум понадобится файл деклараций для нода

Это установит декларации Node API такие, как глобальные require/process/globals, стандартных модули fs/path/stream и прочее. В этом пакете описаны node API последней версии 7.x, что должно подойти и для более старых версий нода.

В более ранних версиях TS, каждый нод модуль должен был быть объявлен. Либо с помощью декларативного файла, либо через заглушку any.

Начиная с версии TS 2.1, описание модуля не является обязательным, если его нет, то предполагается, что модуль имеет тип any (можно вызывать произвольный метод/свойство). Тем не менее TS проверяет то что модуль установлен (загружен), т.е. вы не сможете сбилдить проект пока не установите его npm пакеты.

Следует использовать файлы деклараций с осторожностью, особенно начинающим. Несмотря на преимущества проверки использования сигнатур и лучшее авто дополнение, файлы деклараций имеют так же ряд проблем

Чаще всего файлы деклараций и сами библиотеки пишут разные люди и они не полностью синхронизированы. И бывает в документации описывается валидная сигнатура, которой нет в файле декларации. Кроме того при использовании файлов деклараций, скорее всего придется использовать больше типов в вашем коде и он уже будет сильно отличаться от чистого JS кода.

В своих проектах я часто ограничиваюсь декларациями для node и опционально для lodash.

JWT Object

The JWT Object represents a JWT. It contains the token, the payload, and the state of the JWT.

Indicates if the JWT is expired. will always be false if this is true.

The payload of the JWT (must be an object). jwt-express will add a key-value pair to the payload for .

Indicates if the JWT is stale. The default timeout before a JWT is considered stale is 15 minutes.

The signed token of the JWT.

Indicates if this JWT is valid. This means that the payload hasn’t been tampered with and that the JWT hasn’t expired yet.

Resigns this JWT Objects’s payload.

Calls the revoke function defined in the options with this JWT Object as the first parameter.

Generates a signed token from the payload.

Stores this JWT in the cookie (if configured to use cookies).

Verify the token and load the info into this JWT.

Middleware

  • Visit the Connect wiki for middleware.
  • Grant — OAuth middleware
  • Flashify — for rails type notifications
  • express-uncapitalize — redirect HTTP requests containing uppercase to a canonical lowercase form.
  • express-simple-cdn — easily use a CDN for your static assets, with multiple host support (ex. cdn1.host.com, cdn2.host.com).
  • static-expiry — fingerprinted URLs/Caching Headers for static assets including external domain(s) support.
  • express-slash — Express middleware for people who are anal about trailing slashes.
  • express-debug — unobtrusive development tool that adds a tab with information about req, session, locals, and more to your application.
  • view-helpers — An express middleware that provides common helper methods to the views.
  • express-partial-response — Express middleware for filtering-out parts of JSON responses based on the query-string; à la Google API’s Partial Response.
  • (https://github.com/msemenistyi/connect-image-optimus) — Connect/Express middleware for optimal image serving. Switches to webp/jpegxr if possible.
  • (https://github.com/B3rn475/express-formwork) — a completely asynchronous middleware for form validation and sanitization based on (https://github.com/chriso/validator.js)
  • server-components-express — Express middleware to add helpers for rendering server components and automatically set up server-components-static to serve component static content.

С этим читают