Express
Szybki, minimalistyczny framework Node.js. Przyspiesza pisanie aplikacji.
Instalacja
Express.js można zainstalować w wybranym katalogu na dwa sposoby: tradycyjnie w dwóch krokach:
npm i express -S
Potem (--no-view: instalacja bez szablonów; --view=pug jeżeli jako szablon wybieramy pug; --git: jezeli chcemy automatycznie utworzyć repozytorium git; nazwa katalogu jest opcjonalna, jeżeli nie zostanie podana, użyty zostanie bieżący)
express --no-view --git nazwa_projektu_i_katalogu
Można też użyć generatora, który automatycznie utworzy standardowy szkielet aplikacji: Express application generator
npx express-generator
W obu przypadkach aplikacja powstanie bezpośrednio w bieżącym katalogu. Otrzymamy informacje o tym, że w przyszłych wydaniach jade nie będzie domyślnym szablonem widoków, listę wygenerowanych katalogów i plików oraz instrukcję jak dokończyć instalację.
Niezależnie od wybranej metody, po instalacji w celu zainstalowania wszystkich zależności:
npm install
Które na podstawie package.json dociągnie moduły. Jeżeli widzimy komunikat "run `npm audit fix` to fix them, or `npm audit` for details" to - oczywiście należy się do niego zastosować. Potem uruchomienie aplikacji
DEBUG=express:- npm start
Tutaj widzimy połączone dwa polecenia, pierwsze można wydać tylko raz, potem wystarczy już sam npm start. ak wygląda zalecana w dokumentacji struktura po zakończeniu instalacji.
.
├── app.js
├── bin
│ └── www
├── package.json
├── node_modules
├── public
│ ├── index.html
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes
│ ├── index.js
│ └── users.js
└── views
├── error.pug
├── index.pug
└── layout.pug
7 directories, 9 files
Jeżeli wszystko jest ok, tzn. widzimy stronę pod adresem localhost:3000 ; to zatrzymujemy serwer (Ctrl + C) i git init; .gitignore dla node_modules, można podpiąć repo githuba.
Może się zdarzyć, że nie uruchomi się dając komunikat o zajętym porcie i jeśli nie dają rezultatu "lsof -i :3000" i "killall -9 node" to możemy ustawić inny port tak jak to zaleca stackoverflow:
app.set("port", process.env.PORT || 3001);
Generator domyślnie instaluje Express.js z szablonami jade (choć coraz wyraźniej informuje, że przejdzie na pug), jeżeli chcemy zmienić na pug to, najpierw trzeba pug zainstalować, a potem:
express --view=pug
Kolejną zmianą, którą warto dokonać na początku jest zainstalowanie nodemona, który jak sama nazwa wskazuje jest demonem Node, restartującym serwer po każdej zmianie w monitorowanych plikach. Dzięki temu praca jest płynna i możemy na bieżąco obserwować wyniki działań.
Tak utworzony, gotowy do dalszej pracy katalog zajmuje trochę ponad 10 MB.
Start
Odpowiedź (route) serwera składa się z elementów:
- Nazwa aplikacji (app)
- Metoda HTTP (np. get, post, all)
- W nawiasie:
- Ścieżka
- Funkcja
Przykład:
app.get("/", ()=>{res.send(req.ip)});
Potem w pliku App.js (główny plik ustawień aplikacji, przyjęta domyślna nazwa) - obiekt app stworzony na bazie funkcji express:
const express = require("express");
const app = express();
const port = 3001;
Nasłuchiwanie:
app.listen(port, "127.0.0.1", () => {
console.log("server is listening at port: " + port);
});
Odpowiedź:
app.get("/", (req, res) => {
console.log(req.ip, port);
res.write(req.ip,);
res.write(req.path);
res.end();
});
Uruchomić serwer można przez "node app.js" - w katalogu aplikacji. Jeżeli chcemy używać npm start lub nodemon (nodemon ./bin/www) co jest najwygodniejsze bo tego nie trzeba restartować z każdą zmianą to musimy na końcu app.js zadeklarować eksport.
module.exports = app;
Obiekt Request (req)
Obiekt Request parametry w callbacku każdego zdarzenia, każde zapytanie HTTP. Przykładowe:
- req.hostname nazwa hosta, serwera na którym uruchomiona jest aplikacja
- req.ip adres IP klienta
- req.ips tablica dotychczasowych adresów, oryginalny
- req.method która metoda HTTP przydatne przy ALL, domyślna to get
- req.url z Node.js
- req.originalUrl przy przekierowaniu zachowuje poprzedni adres
- req.path jw choć są różnice
- req.protocol nazwa protokołu: http czy https, string
- req.secure bolean
? query string parametr = wartość &
Dwie możliwości zakodowania niebezpiecznych znaków
encodeURIComponent ręczny
encodeURIComponent('zakodowany tekst z & i innymi niebezpiecznym znakami')
const url = \`/?name=$\{encodeURIComponent(name)}&surname=$(encodeURIComponent(surname)}\`;
URISearchParams z użyciem obiektu
const params = URISearchParams(
{name,
surname }
);
odczytujemy przez req.query, który zawiera wszystkie dane przesłane przez query string, można zastosować destrukturyzację
const {name, surname} = req.query;
req.get metoda pobrania nagłówków (Referer) przesłanych przez klientach, np cookies
Routing metody i ścieżki; REST. W ścieżkach można przekazywać parametry /sciezka/:parametr/:parametr_opcjonalny? (nazewnictwo zgodne z zasadami zmiennych JS, rozdzielone mogą byc tylko kropka, myślnik, slesz), opcjonalny zakończony znakiem zapytania.
req.params odpiera te wszystkie parametry, obiekt, który ma klucze zgodne z wszystkimi parametrami bez dwukropków. Opcjonalny też jest przechwycony jako undefined.
Obiekt Response (res)
res.write() i res.end() znane z Node.js
res.send() łączy obie, ponadto zapewnia:
-
nagłówek Content-Type domyślnie na html i kodowanie
-
Content-Length przeglądarka wie ile zostało ściągnięte
-
nagłówki związane z cachingiem nie pobieranie już pobranych data
-
konwertuje dane jeżeli jest to potrzebne
-
przesyła dane i kończy połączenie
-
string text/html i przesłanie tekstu
-
Buffer application/octet-stream przesyłanie czystych danych
-
array/Object application/json i zakodowanie danych jako JSON
res.json() zawsze wysyła JSONa i ustawia Content-Type na application/json.
res.location() trzeba ustawić kod statusu HTTP (302) i zakończyć połączenie przez res.end(), obie te rzeczy zapewnia sendStatus()
res.redirect() prostszy sam ustawia kod odpowiedzi HTTP (można go zmienić: drugi argument, domyślnie jest 302), tworzy szablon HTML do przekierowania jeżeli standardowe metody zawiodą i daje więcej możliwości, ścieżki specjalne względne (dwie kropki czyli w górę) i cofające ('back' cofa a jeżeli nie ma dokąd to na główną).
- 301 trwałe zapamiętuje nowy adres
- 302 niestałe przekierowanie ale adres zostaje
- 303 zobacz gdzie indziej, jak 302 ale metoda inna niż GET, przekierowująca potem na GET
- 307 jak 302 ale bez zmiany na GET
res.sendFile() potrzebuje ścieżki bezwzględnej, da się to uzyskać modułem path (wbudowany, wystarczy require).
app.get('/logo', (req, res) => {
const fileName = path.join(__dirname, 'sciezka/do/pliku.rozszerzenie');
res.sendFile(fileName);
});
- root zabezpiecza przed wejściem wyżej w katalogach i wystarczy podawać ścieżkę lokalną (static albo public)
- lastModyfied boleanem decydujemy czy ma być ustawiony, domyślnie true
- headers można dodać własne nagłówki
- dotfiles allow/deny/ignore domyślny ignore więc jakby nie było
Można w ten sposób wszystkie pliki wysyłać także HTML i JS jest to szybsze bo pomija Node.js
res.attachment() jw ale wymusi pobranie pliku, ale trzeba zakończyć połączenie przez res.end().
res.download() połączenie 2 powyższych wymusza download i można dodać niektóre opcje znane z res.sendFiles(). Można zmienić nazwę u klienta, nie można stosować parametru root, sam kończy połączenie.
res.set() ustawianie nagłówków (większą liczbę przekazuje się jako obiekt) i cookies.
res.headerSet() informacja czy nagłówki zostały wysłane, przy odpowiedzi najpierw ustawia się nagłówki potem treść, nie można wrócić do nagłówków.
res.cookie() ustawianie ciasteczek, ustawienie nagłówków nazwa i wartość, krótkie czyli sesyjne.
Jako trzeci argument obiektu opcje:
- domain
- expires do kiedy ma być zapamiętane obiekt date
- maxAge jw ale w liczbie ms
- httpOnly domyślnie false, jeśli true frontend nie ma dostępu do ciastka, ważne dla uwierzytelnienia dobrze ustawić wtedy na true.
res.clearCookie(), usuwanie ciasteczek, np do logoutu.
Middleware
Middleware przekształca dane do aplikacji, można ich używać wiele równocześnie. Najczęściej rejestracja middleware przez:
app.use(express.json());
Np express.json() ograniczenie do 100kB bo działa synchronicznie. Wtedy w obiekcie Request pojawi się nowy obiekt req.body, zawierający rozkodowane dane jeśli przybyły w zapytaniu z danymi application/json.
Pobranie danych JSON i odkodowanie do obiektu JS
function showNextQuestion() {
fetch('/question', {method: 'GET',})
.then(r => r.json())
.then(data => {console.log(data)});
}
Rejestrować middleware przed innymi ścieżkami.
express.static() katalog plików statycznych, opcjonalnie drugi parametr pozwalający wybrać stronę główną, ograniczyć widzialność plików z kropką i sterować cache.
Obsługa plików statycznych
const path = require('path');
app.use(express.static(path.join(__dirname, 'public'),));
pakiet cookie-parser do zainstalowania.
const cookieParser = require('cookie-parser');
app.use(cookieParser());
Teraz ciasteczka obecne są w obiekcie req.cookies, podpisane w req.signedCookies.
fetch zapytanie asynchroniczne
App.js
Domyślny plik główny aplikacji.
var createError = require("http-errors"); // przechwytywanie błędów
var cookieSession = require("cookie-session"); // pakiet do sesji ciasteczek
var express = require("express"); // express
var path = require("path"); // ścieżka, moduł podstawowy
var cookieParser = require("cookie-parser"); // do czytania, parsowania ciasteczek
var logger = require("morgan"); // logi w trybie deweloperskim
var config = require("./config"); // wczytanie konfiguracji
var mongoose = require("mongoose"); // połączenie z bazą danych MongoDB
mongoose.connect(
"mongodb+srv://adminCluster0:HASŁO@adrees_bazy.mongodb.net/test?retryWrites=true&w=majority",
{ useNewUrlParser: true }
);
var db = mongoose.connection; // weryfikacja połączenia z bazą MongoDB
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function() {
// we're connected!
console.log("db connected");
});
var indexRouter = require("./routes/index"); // importy routingów
...
var app = express(); // uruchomienie serwera, wywołanie funkcji express
app.set("views", path.join(__dirname, "views")); // katalog z widokami (szablonami)
app.set("view engine", "pug"); // uruchomienie silnika szablonów
// app.use wywołanie expressu, i uruchomienie middleware'ów
app.use(logger("dev"));
app.use(express.json()); // przechwytywanie body i jsona, bezpośrednio
app.use(express.urlencoded({ extended: false })); // dane z formularza, automatyczne parsowanie z postu
app.use(cookieParser()); // ciasteczka
app.use(express.static(path.join(__dirname, "public"))); // katalog plików statycznych, assety, wszystko to co będzie publicznie dostępne
app.use((req, res, next) => {
res.locals.path = req.path;
next();
});
// uruchomienie routingów
app.use("/", indexRouter); // adres routera i nazwa
...
// catch 404 and forward to error handler; przechwytywanie błędów
app.use(function(req, res, next) {
next(createError(404));
});
// error handler; przechwytywanie pozostałych błędów
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
module.exports = app; // eksport aplikacji
Pliki z routingiem muszą być importowane i wywołane w App.js. Importowanie / deklaracja:
const indexRouter = require('./routes/index');
Wywołanie:
app.use('/', indexRouter);
Routing
Poszczególne dokumenty określone w app.js (podstrony) nazywamy routami, ich pliki są w katalogu /routes. Dajmy na to, że chcemy utworzyć dwa: jeden główny domyślny i drugi będący jakimś standardowym dokumentem: będą to index i about. W app.js trzeba je zadeklarować i uruchomić:
const indexRouter = require("./routes/index");
const aboutRouter = require("./routes/about");
app.use("/", indexRouter);
app.use("/about", aboutRouter);
Te pliki w katalogu /routes muszą mieć w deklaracjach Express i Router oraz eksport:
const express = require("express");
const router = express.Router();
module.exports = router;
Żeby coś wyświetlić pośrodku powinna byc funkcja:
router.get("/", function(req, res, next) {
res.render("index", { title: "Express" });
});
Tutaj renderowaniu podlega plik index znajdujący się w domyślnym katalogu szablonów, czyli /views. Jeżeli używamy szablonów pug, będzie to index.pug.
p Witaj w #{title}
Powyższy szablon wyświetli paragraf z treścią "Witaj w Express". Tytuł jest tu parametrem przekazanym z index.js.
Najprostsza weryfikacja
router.get("/login", (res, req) => { // utworzenie routu logowania
res.render("login", {title: "Logowanie"})
}
router.post("/login", (req, res) => { // sprawdzenie
const body = req.body // przypisanie otrzymanego parametru do zmiennej, dane z formularza przychodzą w req pod parametrem body
if (body.login === login & body.password === password) {
res.redirect("/admin")}
else {
res.redirect("/login");
}
})
Logowanie admina ma dwa routery, jeden (get) przechwytuje żądania, drugi (post) weryfikuje poprawność wypełnienia formularza. Ifami przekierowuje się używając redirect. Osobna strona login z formularzem.
Do przechowywania sesji biblioteka cookie-session: klucz sesji i maksymalny czas przechowywania. Sesja przechowywana jest w ciasteczku w komputerze użytkownika. (więcej [96 12m]); importuje się w app.js przed expressem.
Router.all przed dwoma routerami admina. Odbiera wszystko co idzie na ten adres. Weryfikuje istnienie sesji. Jeżeli jest sesja redirect na admina, jeśli nie to na login (tu return, żeby zakończyć funkcję). Żeby wywołać następne funkcje musi być next().
router.all("*", (req, res, next) => {
if (!req.session.admin){ // stan sesji został zapisany w tym parametrze
res.redirect("login");
return;}
next();
})
Przesłanie parametrów np. pomiędzy frontendem a backendem metodą POST:
function sendAnswers(answerIndex) {
fetch(\`/answer/$\{answerIndex}\`), \{method:"POST",}
}
Odbiór na backendzie:
app.post ('/answer/:index', (req, res) => {
const {index} = req.params;
if (question.correctAnswer===Number(index)){
res.json({correct : true,});
else {
res.json({correct : false,});
}
};
});
Krócej:
res.json({correct: question.correctAnswer === Number(index) ? true : false;})
Jeszcze krócej:
res.json({correct: question.correctAnswer === Number(index),});
Szablon
Każdy route typu get musi mieć szablon. Dla powtarzalnych elementów strony, czyli zasadniczego layoutu istnieje plik szablonu o nazwie layout.pug. Jest on importowany przez wszystkie szablony dokumentów deklaracją extends layout, która jest na samej górze i jest pierwszego rzędu. W miejscu przeznaczonym na treść strony zawiera deklarację: block content. Nazwa block content jest zwyczajowa i arbitralna. Jeżeli zostaje wywołany w szablonie strony musi istnieć z tą samą nazwą w layoucie.
Najprostsze menu zdefiniowane w layoucie może wyglądać tak:
nav
li
a(href="/") Strona główna
li
a(href="/about") About
Żeby można było używać styli umieszczonych w katalogu /public/stylesheets/ trzeba zadeklarować katalog statyczny /public:
app.use(express.static(__dirname + "/public"));
W /routes/index.js
res.render("index",{title:"Express})
Router dostaje informacje, który szablon ma wyrenderować pod danym adresem i dodatkowe parametry w obiekcie (w tym wypadku, zakładając, że używamy puga, będzie to /views/index.pug).
Pobranie adresu ścieżki do parametru (w app.js przed użyciem routów - tu ważny jest next(), bo bez niego operacja zakończy się na tym roucie i żadna strona nie zostanie wyświetlona):
app.use(function (req, res, next) {
res.local.path = req.path; // przypisuje otrzymany parametr req do zmiennej lokalnej
next();
})
I potem w szablonie można się testowo odwołać, taki span wyświetli ścieżkę route danej strony:
span=path
Zasadniczy szablon strony zawiera szablon menu. Dlatego menu jest tylko jedno w osobnym pliku szablonu i dzięki extendom przechodzi na layout i jako element layoutu na szablony routów. Mixin menu deklarowany jest w szablonie pug przed elementami strony:
mixin itemActive(title,url)
li
a(href=url class=path==url?'active':'')=title
Menu w szablonie (mixiny jak widać wrzuca się bezpośrednio do puga, dając plusa i parametry w nawiasie:
ul
+itemActive ('Strona główna','/')
+itemActive ('Aktualności','/news')
+itemActive ('Quiz','/quiz')
+itemActive ('Admin','/admin')
Elementom pug można nadawać klasy CSS tak jak każdy atrybut (więcej Attributes), tutaj to jest link z klasą i treścią:
a(href="adres_linku" className="red") Nazwa linku
MongoDB
Modele są określane z dużej litery, trzyma się je w katalogu /models. Określa się typ danych i czy jest to pole wymagane. model musi być zaimportowany. Musi zostać wykonany save(). Pole number to Int32
Połączenie z bazą danych
Trzeba zainstalować mongoose. W app.js najpierw require a potem połączenie:
const mongoose = require("mongoose");
mongoose.connect(
"mongodb+srv://link_do_połączenia",
{ useUnifiedTopology: true, useNewUrlParser: true }
);
Sprawdzenie połączenie jest niezbędne, jeśli chcemy zdiagnozować gdzie nastąpił ewentualny błąd:
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function() {console.log("connected");});
Zapis do bazy danych
Zapisanie newsa metodą post w admin.js:
router.post("/news/add", (req, res) => {
const body = req.body; // zapisanie zmiennej
const newsData = new News(body); // utworzenie zmiennej dla Newsa (model)
const errors = newsData.validateSync(); // zapisanie błędów, walidacja
// console.log(errors);
newsData.save(err => {
if (err) {
// console.log(err);
res.render("admin/news-form", { title: "Dodaj news", errors, body }); // jeżeli wystąpił błąd pozostajemy na formularzu
return;
}
res.redirect("/admin"); // brak błędów, news zapisany, przechodzimy do admina gdzie widać wszystkie zapisane newsy
});
});
Pobranie listy newsów - w parametrze data:
router.get("/", (req, res) => {
News.find({}, (err, data) => {
console.log(data);
res.render("admin/index", { title: "Admin", data }); // pliki admina umieszczone w osobnym katalogu
});
// console.log(req.session.admin);
// res.render("admin/index", { title: "Admin" });
});
news.pug
extends layout
block content
h1= title
p Welcome to #{title}
form(method="get") // wyszukiwarka, formularz działa na query stringu dlatego get
input(type="text" value=search name="search") // value=search zostawia wyszukiwany ciąg w inpucie
input(type="submit" value="Szukaj")
each item in data // listowanie newsów z data i dla każdego wyświetlenie w poniższej strukturze
article
h1(className="title")=item.title
p(className="date")=item.created
p=item.description
Kasowanie
router.get("/news/delete/:id", (req, res) => {
News.findByIdAndDelete(req.params.id, err => { // findByIdAndDelete metoda mongoDB, id jest w params z req
res.redirect("/admin"); // błąd nie jest tu obsługiwany, po poprawnym usunięciu przejście do admina
});
});
Sortowanie i wyszukiwanie
Ponieważ do metody find doda się metody sortowania, to nie ma callbacka od razu po niej, tylko exec. Użyte tutaj find i sort to metody mongoose a nie JS (choć są bardzo podobne). W news.js:
router.get("/", (req, res) => {
const search = req.query.search || ""; // tutaj pusty ciąg znaków na wypadek jeśli search jest undefined, na takim ciągu można wykonać trima, chroni to przed błędem
const foundNews = News.find({ title: new RegExp(search.trim(), "i") }).sort({ // regexp, i pomija wielkość znaków, trim usuwa spacje
created: -1 // sortowanie malejąco, rosnąco 1, 0 sortowanie domyślne
});
foundNews.exec((err, data) => {
res.render("news", { title: "News", data, search });
});
});
Quiz
Dane w poście są w req.body.quiz bo taka jest nazwa radio inputa. Wysłanie quizu.
router.post("/", (req, res) => {
const id = req.body.quiz;
Quiz.findOne({ _id: id }, (err, data) => { // metoda findOne
// console.log(data);
data.vote = data.vote + 1; // zwiększenie o jeden
data.save(err => { // jeśli nie ma błędów zapisanie nowej wartości
req.session.vote = 1; // ustawienie flagi sesji
res.redirect("/quiz"); // i dopiero po wykonaniu save, redirect
});
});
});
Pobranie quizu
router.get("/", (req, res) => {
const show = !req.session.vote; // jeśli negacja sesji jest prawdą pokazanie quizu
Quiz.find({}, (err, data) => {
let sum = 0;
data.forEach(item => {
sum += item.vote;
});
res.render("quiz", { title: "Quiz", data, sum, show });
});
});
API
router.get("/", (req, res) => {
const search = req.query.search || "";
let sort = req.query.sort || defaultSort;
if (sort !== -1 || sort !== 1) {
sort = -1; // ustawienie domyślnej wartości sort
}
const foundNews = News
.find({ title: new RegExp(search.trim(), "i") })
.sort({
created: sort
});
foundNews.exec((err, data) => {
res.json({ data });
});
});
Za pomocą funkcji select() (umieszczonej np. po sort()) możemy ograniczyć liczbę zwracanych pól. Może to wyglądać tak:
.select("_id title description");
Wyszukanie pojedynczego artykułu
router.get("/:id", (req, res) => {
const id = req.params.id;
const foundNews = News.findById(id);
foundNews.exec((err, data) => {
res.json({ data });
});
});
Odnośniki
Strony
- Express minimalist web framework for Node.js - Production best practices: performance and reliability
- MDN: Express web framework (Node.js/JavaScript)
- maitraysuthar/rest-api-nodejs-mongodb Nodejs Expressjs MongoDB Ready-to-use API Project Structure
- antsmartian/lets-build-express contains chapters which explains how one can build a minimal express library
- Mastering JS Express Tutorials
Narzędzia
- eklemen / generate-express Express generator CLI with es6+ support and your choice of database config
- calvintwr / express-routemagic module to automatically require all your express routes
Artykuły
- Medium: hackernoon #express | Flavio Copes "The definitive guide to Express, the Node.js Web Application Framework" | Mike Cronin "Getting Started with Express JS for the Impatient" | Aditya Prakash "Express will dominate the coming decade. Here’s why." | Nick Parsons "Building a Node.js Powered API with Express, Mongoose & MongoDB" | Dicky Perdian "How to fetch data using async / await Express.js, (Based Request)" | Maher Alkendi: "Learn how Full-stack apps work by building a simple one using JavaScript (Part 1)"
- morioh: "How to Create an Express Server Using TypeScript" | David Loffer "How to upload & resize images in Node using Express, Multer and Sharp" | Yoav Reisler "Building a simple Express server in Node.js for Beginners"
- Coursesity Team "9 Best Express JS Courses & Tutorials - Learn Express JS Online"
- Flavio Copes: All the Express tutorials - "Handling forms in Express" | "Send files using Express"
- rwieruch: "How to setup Express.js in Node.js" | "How to create a REST API with Express.js in Node.js"
- Dan Englishby "Setting Up A Local Web Server With NodeJS & ExpressJS"
- Dan Arias "Use TypeScript to Create a Secure API with Node.js and Express: Getting Started"
- Tutorial And Example: "Express.js Tutorial for Beginners" | "ExpressJS Interview Questions"
- "Express Error Handling: Tips & Tricks"
- Paweł Chudzik "From express and pug to HTML"
- Olumyco "FastLearn JavaScript Tutorial: Understanding Express Framework (Configuration, Middlewares & Routing) At A Goal"
- "Express.js Web Application"
- "ExpressJS - Random Number Generator"
- Elliot Blackburn "Understanding middleware in Express.js"
- Quickcode "Top Tutorials to Learn Express JS Framework in Node Js"
- Janith Kasun Colombo "Building a REST API with Node and Express"
- StackChief "Express Error Handling: Tips & Tricks"
- Simon Plenderleith "5 best practices for building a modern API with Express"
Youtube
- **Playlisty:*- overment "Kurs Node.js (Express.js)" [YT playlista 12 ilmóœ] | Coding Shiksha "Real Time Chat App Using Socket.IO and ExpressJS" [YT playlist 7 filmów] | codedamn "NodeJS + ExpressJS Tutorials" [YT playlist 26 filmów] | Fullstack Programmer "Advanced Express JS REST API Tutorial/Crash Course | Build REST API with Express & Node" [YT playlist 7 filmów] | Okay Dexter "Express & Nunjucks Tutorials" [YT playlist 20 filmów] | productioncoder "Sessions in express.js | Node.js session implementation with Redis" [YT playlista 9 filmów] | Stuy "Backend Development" [YT playlista 10 filmów] | The Nerdy Dev "Making a Simple Messenger API Using Express" [YT playlista 5 filmów] | CodeTech Facts "Express Tutorial" [YT playlist 6 filmów]
- **Filmy:*- Samuraj Programowania "Wprowadzenie do Express (Framework Node.js)" [YT 1:10:22] | Hays Stanford "ExpressJS Crash Course | The Ultimate Guide to REST APIs & More!" [YT 46:00] | "Complete Node Express MongoDB CRUD Application in Under 2 Hours" [1:39:53] | "Introduction to Node & Express" [2:13:09]
- freeCodeCamp.org "Node.js and Express.js - Full Course" [YT 8:16:47]
- Traversy Media Traversy Media "Node.js & Express From Scratch" [YT playlist 12 fimów] | Traversy Media "ExpressJS Crash Course" [YT 1:15:36] | Traversy Media "Node.js App From Scratch | Express, MongoDB & Google OAuth" [2:28:42]
Typescript
Inne frameworki
- LoopBack
- NestJS - "TypeORM With NEST JS Basic Tutorial" | "Learn NestJS - Full Course for Beginners" [YT 3:19:12]
- Sails.js - Realtime MVC Framework for Node.js
- NextJS - więcej na ten temat Next.js