
Express + TypeScript - konfiguracja projektu
Wstęp
Opisuje tutaj prosty sposób na rozpoczęcie pracy z TypeScripty i Node a dokładniej z jego frameworkiem Express. W tym wpisie powstanie całkiem prosty boilerplate aplikacji, która w kolejnych artykułach rozwijana będzie o coraz to bardziej zaawansowane elementy. Omówię konfiguracje projektu, strukturę aplikacji, integracje z bazami danych o nie tylko.
Ten wpis, jak i kolejne, zawiera kompletny omawiany kod w repozytorium github.
- Express + TypeScript - konfiguracja projektu
- Express + TypeScript - ESLint i Prettier
- Express + TypeScript - CRUD boilerplate
- Express + TypeScript - struktura aplikacji
- Express + TypeScript - konfiguracja MongoDB
- Express + TypeScript - walidacja requestów z biblioteką Joi
- Express + TypeScript - middlewares aplikacji
Właściwie po co?
Express.JS
Express jest to jeden z bardziej popularnych frameworków Node, służący do pisania aplikacji backendowych. Jest prosty, minimalistyczny i bardzo niewiele narzuca programiście. Struktura aplikacji może być praktycznie dowolna, a framework nie zmusza do stosowania żadnego patternu, internet natomiast pełen jest dobrych praktyk i wzorców pisania aplikacji.
Express świetnie sprawdza się do budowania API typu REST i aplikacji CRUD, oraz adaptowania w aplikacjach różnego rodzaju baz danych. Prosta aplikacja pisana w tym i kolejnych artykułach będzie wykorzystywać prostotę i wielofunkcyjność framewoku oraz bogactwo różnych możliwości rozszerzania aplikacji w nim pisanych.
Zazwyczaj, gdy pojawia się hasło Express.JS, od razu nasuwa się na myśl Rest'owe API napisane w JaVaScript. Będąc świadomym ograniczeń JS wynikających z braku typowania, ale nadal chcąc użyć prostego, dobrze sprawdzającego się i pozwalającego na dowolną rozbudowę aplikacji frameworka, jakim jest Express? Postanowiłem napisać aplikację przy użyciu TypeScripta? Jak się niedługo okaże, framework i język współpracują ze sobą świetnie!
Początek
Pierwszym krokiem powinno być sprawdzenie, że posiadam zainstalowane na komputerze Node i NPM.
node -v
npm -v
Później standardowo przenoszę się do miejsca, w którym chcę rozpocząć projekt. W wybranym katalogu, jak w przypadku każdego projektu w Node, rozpoczynam od inicjalizacji projektu. Flaga -y pomija wszystkie pytania, które zadaje nam CLI i uzupełnia je defaultami.
npm init -y
Obok, w ten sposób stworzonego pliku package.json, tym samym katalogu zakładam folder /src a w nim plik index.js, który będzie wyjściowym plikiem aplikacji.
W pliku package.json zapisywane są wszystkie paczki, z których korzystam w projekcie. Poza dependencjami, zapisane tu są również podstawowe dane o projekcie. Skupiam się tu jedynie na przygotowaniu startera do późniejszego wykorzystania w innych aplikacjach. Na początek trzeba tylko zmienić pole "main" z index.js na src/index.js zgodnie z drzewem plików, który utworzyłem wyżej.
{
"name": "express-ts",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Teraz, aby pokazać, że aplikacja w ogóle działa, nie skupiając się jeszcze na tym, co oferuje TypeScript i Express, wypełniam plik src/index.js najprostszym możliwym "serwerem", którego jedynym zadaniem jest nasłuchiwanie na localhost:5000/ i zwracanie "Hello World!". Warto zauważyć, że pierwsza linijka importuje express w nowy sposób, wskazujący na importowanie zależności na podstawie modułów. Jest to możliwe przez podanie w package.json typu aplikacji jako "type": "module".
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.listen(5000, () => {
console.log("Server started on port 5000!");
});
Aby być w stanie odpalić serwis, muszę zainstalować jego dependencje, czyli sam Express framework.
npm i express
Po instalacji aplikację można wystartować w terminalu, z poziomu katalogu projektu:
node src/index.js
W terminalu pojawiła się informacja, że aplikacja wystartowała na porcie 5000, a po wywołaniu w przeglądarce adresu http://localhost:5000 ukaże się tylko "Hello world!".
Inicjalizacja TypeScript
Aby móc używać w aplikacji TypeScript i dobrodziejstw z niego płynących, zainstaluję go jak dev-dependency wraz bardzo przydatnymi deklaracjami typów dla Node i Express. Deklaracje typów to paczki, które można zainstalować poprzez NPM, zawierające nazwę paczki, dla której typów potrzebuję, poprzedzone @types/..., predefiniujące typy i kształty obiektów, wykorzystywanych przez daną paczkę. W tym wypadku będzie to @types/express i ogólny @types/node.
npm i -D typescript @types/express @types/node
Kolejnym krokiem jest inicjalizacja TypeScrypt w projekcie.
npx tsc --init
W ten sposób generuję plik tsconfig.json, w którym zapisane są wszystkie parametry, mówiące o tym w jaki sposób TS ma budować projekt (jako budowanie rozumiem generowanie - kompilacje, plików JS, które później będą wykorzystywane do działania aplikacji). Na sam początek dostajem bardzo duży plik z wieloma wykomentowanymi parametrami, można spokojnie usunąć wszystko, co wykomentowane.
Najważniejsze parametry tsconfig.json:
- target: do jakiej wersji JS kompilowane będą pliki
- module: w jaki sposób ma zachowywać się rozwiązywanie modułów w skompilowanym kodzie
- strict: czy używać strict type-checking
Pozostałe defaulty mogą pozostać bez rozwinięcia.
Dodatkowo parametrami, które warto przekazać w tsconfig.json są rootDir i outDir, które mówią odpowiednio o tym, z jakiego katalogu ma być zaciągany kod .ts i do jakiego ma być zapisywany generowany kod .js.
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
Po tak dodanym kodzie TypeScrypt do projektu można zmienić sam plik src/index.js na src/index.ts. Również jego zawartość nieznacznie się zmieni, aby wykorzystać zainstalowane wcześniej deklaracje typów.
import express, { Request, Response } from "express";
const app = express();
app.get("/", (req: Request, res: Response) => {
res.send("Hello World!");
});
app.listen(5000, () => {
console.log("Server started on port 5000!");
});
Tak naprawdę wcale nie trzeba by było zmieniać zawartości pliku
src/index.ts. Czysty kod JS jest w 100% rozumiany przez TS, więc aplikacja działałaby tak samo.
Tak przebudowany projekt pisany w TS możena teraz zkompilować do plików JS jedną komendą terminala: npx tsc.
Pliki .js generują się w katalogu /dist, więc aby odpalić aplikację, będę wywoływać plik właśnie tam się znajdujący.
node dist/index.js
Skrypty w package.json
Aby usprawnić pracę kodem, defininiuję skrypty w package.json odpalające pod spodem zdefiniowane wcześniej komendy budowania i odpalania aplikacji.
{
"name": "express-ts",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"build": "npx tsc",
"start": "node dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"@types/express": "^4.17.14",
"@types/node": "^18.11.13",
"typescript": "^4.9.4"
}
}
rebuild & restart on file change
Ostatnią godną uwagi rzeczą, jest automatyczny rebuild i restart aplikacji, gdy pliki .ts się zmieniły. Pomogą w tym dwa pakiety, które zainstaluję jak devDepenencje.
npm i -D nodemon ts-node
Można teraz dodać w package.json kolejny skrypt, "start:dev": "nodemon src/index.ts", który po odpaleniu będzie obserwował wszystkie pliki .ts, gdy któryś się zmieni, przebuduje aplikację i ponownie ją odpali, już ze zmienionym kodem.
{
"name": "express-ts",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"build": "npx tsc",
"start": "node dist/index.js",
"start:dev": "nodemon src/index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"@types/express": "^4.17.14",
"@types/node": "^18.11.13",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
}
Podsumowanie
Tak stworzony boilerplate może posłużyć do tworzenia kolejnych, bardziej zaawansowanych projektów przy użyciu frameworku Express, a dzięki wsparciu TypeScript kod będzie zawsze czytelny, wolny od oczywistych błędów związanych z typowaniem i optymalnie skompilowany pod produkcję. Cały kod powyższej aplikacji można znaleźć w repozytorium github.