Proste RESTowe API (Node.js + express.js + TypeScript + MongoDB) cz.1
Moi drodzy, ruszamy na back-end! Dzisiaj będziemy pisać bardzo proste RESTowe API, które pomoże każdemu frontowi stworzyć prosty back-end do swojego projektu.
Historia zaczyna się od tego, że w newsletterze dostałam linka o artykule o prostym set upie node.js + express.js + Typescript. Gdzieś ostatnio myślałam o przepisaniu portfolio na jakiś reaktywny framework JS, a do tego przydałoby się API. Akurat miałam wolny sobotni wieczór, a Youtube podpowiedział mi hit Pawła Domagały.
Czy muszę mówić coś więcej? Zapraszam :)
Słowem wstępu
Jak zwykle zaczynam od sekcji dla początkujących, ale trudno mi wyjaśnić wszystkie aspekty opisane w niniejszym poście. Zdecydowanie sekcja dla początkujących jest skierowana dla osób, które miały do czynienia z podstawami programowania obiektowego.
NodeJS to środowisko uruchomieniowe, które pozwala na interpretację JavaScriptu, stworzone pod pisanie serwerów i aplikacji internetowych.
Express.js to jedna z popularniejszych bibliotek do budowania serwisów opartych o protokół HTTP. Obsługuje wiele popularnych silników renderowania, ale dzisiaj nie będziemy z nich korzystać.
TypeScript to silnie typowany język programowania, który przetwarza się na JavaScript (tzw. nadzbiór języka). Pozwala na programowanie obiektowe oparte na klasach oraz typach :) Osobiście podsumowałabym to jako całkiem udane dziecko C# i JavaScriptu, rozwijane pod skrzydłami Microsoftu.
REST - albo w sumie RESTowe (ang. RESTful) serwisy - to bardzo popularna koncepcja projektowania web serwisów.
NPM - manager do zarządzania pakietami JavaScript.
TSLint - wygodne sprawdzanie kodu napisanego w TypeScript, osobiście używam go w edytorze VS Code.
Rozpoczynamy!
Utwórzmy sobie folder, wejdźmy do niego w konsoli i szalejmy:
npm init
Tutaj swobodnie wpiszcie sobie dane projektu przez konfigurator; nie przejmujcie się, to można edytować w pliku.
npm install -D typescript
npm install -D tslint
npm install -D nodemon
npm install express
npm install @types/express
npm install mongoose
npm install @types/mongoose
Powyższe komendy trzeba puścić w konsoli. Ogólnie @types to są definicje typów bibliotek w TypeScript, mongoose to biblioteka do operacji bazowo-danowych w MongoDB.
Nodemon służy do przyjemnego programowania w nodeJS, nasłuchuje i przeładowuje środowisko po zmianach. TSLint podpowie nam składnię czy też strukturę obiektów.
Konfiguracja
Poniższą komendą tworzymy plik konfiguracyjny TSLint. Najlepiej pobrać tslint:recommended
i nadpisać to co się nam nie podoba w rules.
./node_modules/.bin/tslint --init
Tak wygląda mój po drobnych zmianach:
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"interface-name": [false],
"no-console": [false],
"quotemark": [true, "single"],
"object-literal-sort-keys": false
},
"rulesDirectory": []
}
Teraz edytujemy plik package.json i dołożymy tam skróty skryptów, żeby łatwiej było nam uruchamiać aplikację :)
Tak wygląda mój plik package.json:
{
"name": "api",
"version": "1.0.0",
"description": "Przykładowy opis",
"main": "dist/server.js",
"scripts": {
"build-ts": "tsc",
"start": "npm run serve",
"serve": "node dist/server.js",
"watch-node": "nodemon dist/server.js",
"watch-ts": "tsc -w"
},
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "^1.18.4",
"tslint": "^5.11.0",
"typescript": "^3.0.3"
},
"dependencies": {
"@types/express": "^4.16.0",
"@types/mongoose": "^5.2.17",
"express": "^4.16.3",
"mongoose": "^5.2.17"
}
}
Nasza baza - MongoDB lokalnie
Ze strony MongoDB należy pobrać instalator lub paczkę i zainstalować, a potem uruchomić plik exe. Osobiście korzystam z konsoli Bashowej :)
Windowsowo będzie to wyglądało na przykład:
cd twojasciezka/MongoDB/Server/3.4/bin
./mongod.exe
To dwa polecenia. Napisane są osobno, żebyście mogli wykryć błędną ścieżkę.
Jeśli twoją konsolę zalał multum informacji i nie pokazał się żaden error, osiągnęliśmy sukces i przechodzimy do programowania.
Zadanie
Chcę, żeby adres /projects zwrócił mi listę projektów w formacie JSON.
To do roboty!
Najpierw uruchomimy aplikację z biblioteką express.js. Tworzymy plik server.ts w folderze src:
/src/server.ts
import express from 'express';
const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(3000, () => console.log('Example app listening on port 3000!'));
W tej chwili po wywołaniu:
npm run build-ts
npm run start
W konsoli powinien się nam pojawić komunikat: `Example app listening on port 3000!`, a na http://localhost:3000 powinien być tekst 'Hello Word!'
Teraz włączymy na słuchiwanie, żebyśmy nie musieli powyższych komend wywoływać po każdej zmianie. Do tego musimy otworzyć 2 konsole:
npm run watch-node
npm run watch-ts
API
Całość będzie wykonana w popularnym wzorcu architektonicznym MVC, w dość uproszczonej wersji. W ogóle będzie ekstremalnie prosto: Nie będzie repo, interfejsy ograniczymy do minimum (czasem nawet niebezpiecznego, o czym opowiem) i czasem wrzucimy parę rzeczy do jednego pliku. Nie będzie także żadnej autoryzacji, co jest ogólnie zła praktyką.
W poniższym pliku server.ts mamy znane z początku wywołanie aplikacji oraz parę nowości :)
/src/server.ts
import express from 'express';
import mongoose from 'mongoose';
const app = express();
import * as projectController from './controllers/project';
mongoose.connect('mongodb://localhost:27017/').then(
() => {
console.log('Udało się!');
},
).catch((err) => {
console.log('MongoDB connection error. Please make sure MongoDB is running. ' + err);
// process.exit();
});
app.get('/', (req, res) => res.send('Hello World!'));
app.get('/projects', projectController.getProjects);
app.listen(3000, () => console.log('Example app listening on port 3000!'));
Przede wszystkim metoda mongoose.connect
w której łączymy się z bazą danych. Łączenie się z lokalną bazą nie wymaga podania nazwy użytkownika oraz hasła, a port 27017 jest domyślny dla MongoDB. Wszystkie ustawienia mogliście zmienić podczas instalacji. Czy muszę wspominać, że trzymanie danych do bazy w pliku server.ts to zły pomysł i jest to tylko na potrzeby tutorialu? Myślę, że nie.
Ale wracając do kodu: mongoose.connect
zwraca obietnicę (ang. Promise) i jeśli będzie ona sukcesem to mamy wesoły komunikat :) Jeśli nie to odpowiedź zwróci wyjątek, który złapiemy catch'em i mamy smutna informację z wyjaśnieniem. Można też - jak w zakomentowanym fragmencie - wyłączyć aplikację poleceniem process.exit().
Kolejną nowością jest przypisanie ścieżek do funkcji, czyli zdefiniowanie routingów.
http://localhost:3000 zwróci nam Hello World!
http://localhost:3000/projects zwróci nam to co zwraca funkcja, którą przypisaliśmy.
Teraz przechodzimy do import z kontrolerem, gdzie ładujemy cały kontroler i przypisujemy poszczególną funkcję do routingu.