Када се прави позадина за РЕСТ АПИ, Екпресс.јс је често први избор међу Ноде.јс оквирима. Иако такође подржава изградњу статичког ХТМЛ-а и шаблона, у овој серији ћемо се фокусирати на позадински развој користећи ТипеСцрипт. Добијени РЕСТ АПИ ће бити онај који би било који фронт-енд фрамеворк или екстерни бацк-енд сервис могао да тражи.
Мораћете:
У терминалу (или командној линији) направићемо фасциклу за пројекат. Из те фасцикле покрените npm init
. То ће створити неке од основних датотека Ноде.јс пројекта које су нам потребне.
Следеће ћемо додати Екпресс.јс оквир и неке корисне библиотеке:
npm install --save express debug winston express-winston cors
Постоје добри разлози због којих су ове библиотеке Ноде.јс програмер фаворити:
debug
је модул који ћемо користити да избегнемо позивање console.log()
док развијамо нашу апликацију. На овај начин лако можемо филтрирати изјаве за отклањање грешака током решавања проблема. Такође се могу у потпуности искључити у производњи, уместо да их треба ручно уклањати.winston
одговоран је за евидентирање захтева за наш АПИ и враћене одговоре (и грешке). express-winston
интегрише се директно са Екпресс.јс, тако да сви стандардни АПИ-ји winston
код за евидентирање је већ завршен.cors
је део Екпресс.јс посредничког софтвера који нам омогућава да то омогућимо дељење ресурса више порекла . Без тога би наш АПИ био употребљив само са предњих крајева који се сервирају са потпуно исте поддомене као и са наше задње стране.Наш задњи крај користи ове пакете када је покренут. Али такође морамо инсталирати неке развој зависности за нашу ТипеСцрипт конфигурацију. За то ћемо покренути:
npm install --save-dev @types/cors @types/express @types/debug source-map-support tslint typescript
Ове зависности су потребне да би се омогућио ТипеСцрипт за сопствени код наше апликације, заједно са типовима које користи Екпресс.јс и друге зависности. Ово може уштедети пуно времена када користимо ИДЕ попут ВебСторм или ВСЦоде, омогућавајући нам да аутоматски довршимо неке функције током кодирања.
Коначне зависности у package.json
треба да буде овако:
'dependencies': { 'debug': '^4.2.0', 'express': '^4.17.1', 'express-winston': '^4.0.5', 'winston': '^3.3.3', 'cors': '^2.8.5' }, 'devDependencies': { '@types/cors': '^2.8.7', '@types/debug': '^4.1.5', '@types/express': '^4.17.2', 'source-map-support': '^0.5.16', 'tslint': '^6.0.0', 'typescript': '^3.7.5' }
Сада када смо инсталирали све потребне зависности, кренимо да правимо сопствени код!
За ово упутство ћемо створити само три датотеке:
./app.ts
./common/common.routes.config.ts
./users/users.routes.config.ts
Идеја иза две фасцикле структуре пројекта (common
и users
) је да имају појединачне модуле који имају своје одговорности. У том смислу, на крају ћемо имати неке или све следеће за сваки модул:
Ова структура директоријума пружа рану полазну тачку за остатак овог упутства и довољно за почетак вежбања.
У common
, креирајмо common.routes.config.ts
датотека да изгледа овако:
import express from 'express'; export class CommonRoutesConfig { app: express.Application; name: string; constructor(app: express.Application, name: string) { this.app = app; this.name = name; } getName() { return this.name; } }
Начин на који овде креирамо руте није обавезан. Али пошто радимо са ТипеСцрипт-ом, сценарио наших рута је прилика да вежбамо коришћење наследства са extends
кључна реч, као што ћемо видети ускоро. У овом пројекту, све датотеке руте имају исто понашање: Имају име (које ћемо користити за уклањање грешака) и приступ главном Екпресс.јс Application
објект.
Сада можемо започети са креирањем датотеке корисника. На users
директоријум, креирајмо users.routes.config.ts
и почните да га кодирате овако:
import {CommonRoutesConfig} from '../common/common.routes.config'; import express from 'express'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, 'UsersRoutes'); } }
Ево, увозимо CommonRoutesConfig
класе и проширујући је на нашу нову класу, која се зове UsersRoutes
. Са конструктором шаљемо апликацију (главни express.Application
објекат) и име УсерсРоутес конструктору CommonRoutesConfig
Овај пример је прилично једноставан, али када скалирамо како бисмо створили неколико датотека руте, ово ће нам помоћи да избегнемо дуплирани код.
Претпоставимо да бисмо желели да у ову датотеку додамо нове функције, као што је евидентирање. Могли бисмо додати потребно поље у CommonRoutesConfig
класе, а затим све руте које се протежу CommonRoutesConfig
имаће приступ.
утицај боје на људско понашање
Шта ако бисмо желели да имамо неку функционалност која је слично између ових класа (попут конфигурисања АПИ крајњих тачака), али за то је потребна различита имплементација за сваку класу? Једна од могућности је употреба ТипеСцрипт функције тзв одвајање .
Направимо врло једноставну апстрактну функцију која UsersRoutes
класа (и будуће класе рутирања) наследиће од CommonRoutesConfig
. Рецимо да желимо да присилимо све руте да имају функцију (тако да је можемо назвати из нашег заједничког конструктора) која се зове configureRoutes()
. Ту ћемо прогласити крајње тачке ресурса сваке класе рутирања.
Да бисмо то урадили, додаћемо три брзе ствари у common.routes.config.ts
:
abstract
до нашег class
линија, како би се омогућила апстракција за ову класу.abstract configureRoutes(): express.Application;
. Ово присиљава било коју класу која се протеже CommonRoutesConfig
да обезбеди имплементацију која се подудара са тим потписом - ако се не догоди, преводилац ТипеСцрипт ће избацити грешку.this.configureRoutes();
на крају конструктора, јер сада можемо бити сигурни да ће ова функција постојати.Резултат:
import express from 'express'; export abstract class CommonRoutesConfig { app: express.Application; name: string; constructor(app: express.Application, name: string) { this.app = app; this.name = name; this.configureRoutes(); } getName() { return this.name; } abstract configureRoutes(): express.Application; }
Уз то, било која класа која се протеже CommonRoutesConfig
мора имати функцију која се зове configureRoutes()
који враћа express.Application
објект. То значи users.routes.config.ts
треба ажурирати:
import {CommonRoutesConfig} from '../common/common.routes.config'; import express from 'express'; export class UsersRoutes extends CommonRoutesConfig { constructor(app: express.Application) { super(app, 'UsersRoutes'); } configureRoutes() { // (we'll add the actual route configuration here next) return this.app; } }
Као резиме онога што смо направили:
Прво увозимо common.routes.config
датотека, затим express
модул. Затим дефинишемо UserRoutes
класе, рекавши да желимо да прошири CommonRoutesConfig
основна класа, што подразумева да обећавамо да ће применити configureRoutes()
.
Да бисте послали информације на CommonRoutesConfig
класе, користимо constructor
разреда. Очекује да ће добити express.Application
објекта, што ћемо детаљније описати у следећем кораку. Са super()
прелазимо на конструктор CommonRoutesConfig
апликације и име наших рута, што је у овом сценарију УсерсРоутес. (super()
, заузврат, позваће нашу имплементацију configureRoutes()
.)
Тхе configureRoutes()
функција је место где ћемо створити крајње тачке за кориснике нашег РЕСТ АПИ-ја. Тамо ћемо користити апликација и његове рута функционалности из Екпресс.јс.
Идеја у коришћењу app.route()
функција је да се избегне дуплирање кода, што је лако јер креирамо РЕСТ АПИ са добро дефинисаним ресурсима. Главни ресурс овог водича је корисника . У овом сценарију имамо два случаја:
users
на крају тражене путање. (У овом чланку нећемо улазити у филтрирање упита, пагинацију или друге такве упите.)users/:userId
Начин .route()
ради у Екпресс.јс-у омогућава нам руковање ХТТП глаголима са неким елегантним уланчавањем. То је зато што .get()
, .post()
, итд., Враћају исту инстанцу IRoute
да је први .route()
цалл доес. Коначна конфигурација биће овако:
configureRoutes() { this.app.route(`/users`) .get((req: express.Request, res: express.Response) => { res.status(200).send(`List of users`); }) .post((req: express.Request, res: express.Response) => { res.status(200).send(`Post to users`); }); this.app.route(`/users/:userId`) .all((req: express.Request, res: express.Response, next: express.NextFunction) => { // this middleware function runs before any request to /users/:userId // but it doesn't accomplish anything just yet--- // it simply passes control to the next applicable function below using next() next(); }) .get((req: express.Request, res: express.Response) => { res.status(200).send(`GET requested for id ${req.params.userId}`); }) .put((req: express.Request, res: express.Response) => { res.status(200).send(`PUT requested for id ${req.params.userId}`); }) .patch((req: express.Request, res: express.Response) => { res.status(200).send(`PATCH requested for id ${req.params.userId}`); }) .delete((req: express.Request, res: express.Response) => { res.status(200).send(`DELETE requested for id ${req.params.userId}`); }); return this.app; }
Горњи код омогућава било ком клијенту РЕСТ АПИ да позове наш users
крајња тачка са POST
или а GET
захтев. Слично томе, омогућава клијенту да позове наш /users/:userId
крајња тачка са GET
, PUT
, PATCH
или DELETE
захтев.
Али за /users/:userId
, додали смо и генерички посреднички софтвер помоћу all()
функција која ће се покретати пре било ког од get()
, put()
, patch()
или delete()
функције. Ова функција ће бити корисна када (касније у серији) креирамо руте којима је намењен приступ само овјереним корисницима.
Можда сте то приметили у нашем .all()
функција - као и код било ког дела посредничког софтвера - имамо три врсте поља: Request
, Response
и NextFunction
.
NextFunction
служи као функција повратног позива, омогућавајући контроли да пролази кроз било које друге функције међуопреме. Успут, сав посреднички софтвер ће делити исти објекат захтева и одговора пре него што контролер коначно пошаље одговор подносиоцу захтева.app.ts
Сада када смо конфигурисали неке основне скелете рута, започет ћемо конфигурисање улазне тачке апликације. Створимо app.ts
датотеку у корену фасцикле нашег пројекта и започните је овим кодом:
import express from 'express'; import * as http from 'http'; import * as bodyparser from 'body-parser'; import * as winston from 'winston'; import * as expressWinston from 'express-winston'; import cors from 'cors'; import {CommonRoutesConfig} from './common/common.routes.config'; import {UsersRoutes} from './users/users.routes.config'; import debug from 'debug';
Само два од ових увоза су нова у овом тренутку у чланку:
http
је Ноде.јс-изворни модул. Потребно је да покренете нашу Екпресс.јс апликацију.body-parser
је међуопрема која долази са Екпресс.јс. Анализира захтев (у нашем случају као ЈСОН) пре него што контрола пређе на наше властите обрађиваче захтева.Сада када смо увезли датотеке, почећемо да декларишемо променљиве које желимо да користимо:
извештај о токовима готовине из биланса стања
const app: express.Application = express(); const server: http.Server = http.createServer(app); const port: Number = 3000; const routes: Array = []; const debugLog: debug.IDebugger = debug('app');
Тхе express()
функција враћа главни објект Екпресс.јс апликације који ћемо проћи кроз наш код, почевши од додавања у http.Server
објект. (Морат ћемо покренути http.Server
након конфигурације нашег express.Application
.)
Слушаћемо на порту 3000 уместо на стандардним портовима 80 (ХТТП) или 443 (ХТТПС), јер би се они обично користили за предњи крај апликације.
Не постоји правило да лука треба бити 3000 - ако није одређено, произвољна лука биће додељено - али 3000 се користи у примерима документације и за Ноде.јс и за Екпресс.јс, тако да овде настављамо традицију.
И даље можемо локално да радимо на прилагођеном порту, чак и када желимо да наш задњи крај одговара на захтеве на стандардним портовима. Ово би захтевало обрнути прокси за пријем захтева на порт 80 или 443 са одређеним доменом или поддоменом. Затим би их преусмерио на наш интерни порт 3000.
Тхе routes
арраи ће пратити датотеке наших рута у сврхе отклањања грешака, као што ћемо видети у наставку.
Коначно, debugLog
завршиће као функција слична console.log
, али боље: Лакше је фино подесити јер се аутоматски поставља у опсег на оно што желимо да зовемо контекст датотеке / модула. (У овом случају, назвали смо га „апликација“ када смо то проследили у низу конструктору debug()
).)
Сада смо спремни да конфигуришемо све наше Екпресс.јс модуле међуопреме и руте нашег АПИ-ја:
// here we are adding middleware to parse all incoming requests as JSON app.use(bodyparser.json()); // here we are adding middleware to allow cross-origin requests app.use(cors()); // here we are configuring the expressWinston logging middleware, // which will automatically log all HTTP requests handled by Express.js app.use(expressWinston.logger({ transports: [ new winston.transports.Console() ], format: winston.format.combine( winston.format.colorize(), winston.format.json() ) })); // here we are adding the UserRoutes to our array, // after sending the Express.js application object to have the routes added to our app! routes.push(new UsersRoutes(app)); // here we are configuring the expressWinston error-logging middleware, // which doesn't *handle* errors per se, but does *log* them app.use(expressWinston.errorLogger({ transports: [ new winston.transports.Console() ], format: winston.format.combine( winston.format.colorize(), winston.format.json() ) })); // this is a simple route to make sure everything is working properly app.get('/', (req: express.Request, res: express.Response) => { res.status(200).send(`Server up and running!`) });
Можда сте приметили да expressWinston.errorLogger
је постављено после ми дефинишемо наше руте. Ово није грешка! Као екпресс-винстон документација државе:
Регистратор треба додати НАКОН експресног рутера (
app.router
) И ПРЕД било којим од ваших прилагођених руковалаца грешкама (express.handler
).
Коначно и најважније:
server.listen(port, () => { debugLog(`Server running at http://localhost:${port}`); routes.forEach((route: CommonRoutesConfig) => { debugLog(`Routes configured for ${route.getName()}`); }); });
Ово заправо покреће наш сервер. Једном када је покренуто, Ноде.јс ће покренути нашу функцију повратног позива која извештава да радимо, праћена именима свих рута које смо конфигурисали - до сада, само UsersRoutes
package.json
на Транспиле ТипеСцрипт у ЈаваСцрипт и покрените апликацијуСада када имамо свој скелет спреман за рад, прво нам је потребна нека конфигурација узорка да би се омогућила ТипеСцрипт транспилација. Додајмо датотеку tsconfig.json
у корену пројекта:
{ 'compilerOptions': { 'target': 'es2016', 'module': 'commonjs', 'outDir': './dist', 'strict': true, 'esModuleInterop': true, 'inlineSourceMap': true } }
Тада само треба да додамо последње потезе у package.json
у облику следећих скрипти:
'scripts': { 'start': 'tsc && node ./dist/app.js', 'debug': 'export DEBUG=* && npm run start', 'test': 'echo 'Error: no test specified' && exit 1' },
Тхе test
скрипта је резервирано место које ћемо заменити касније у низу.
Тхе тсц у start
скрипта припада ТипеСцрипт-у. Одговоран је за превођење нашег ТипеСцрипт кода у ЈаваСцрипт, који ће избацити у dist
директоријум. Затим само покрећемо уграђену верзију са node ./dist/app.js
.
Тхе debug
скрипта позива start
скрипта, али прво дефинише DEBUG
променљива околине. Ово има за последицу омогућавање свих наших debugLog()
изјаве (плус сличне из самог Екпресс.јс-а, који користи исти debug
модул као и ми) за излаз корисних детаља на терминал - детаље који се (погодно) иначе скривају приликом покретања сервера у производном режиму са стандардним npm start
.
Покушајте да покренете npm run debug
себе, а затим упоредите то са npm start
да бисте видели како се мења излаз конзоле.
Савет: Излаз за отклањање грешака можете ограничити на наш app.ts
датотека је сопствена debugLog()
изјаве помоћу DEBUG=app
уместо DEBUG=*
. Тхе debug
модул је генерално прилично флексибилан и ова функција није изузетак .
Корисници Виндовс-а ће вероватно морати да промене export
до SET
пошто export
је како то функционише на Мац-у и Линук-у. Ако ваш пројекат треба да подржи више развојних окружења, цросс-енв пакет овде пружа директно решење.
Са npm run debug
или npm start
још увек траје, наш РЕСТ АПИ биће спреман за сервисирање захтева на порту 3000. У овом тренутку можемо да користимо цУРЛ, Поштар , Несаница итд. за тестирање задњег дела.
Будући да смо креирали само скелет за кориснички ресурс, можемо једноставно да шаљемо захтеве без тела да бисмо видели да ли све функционише како се очекује. На пример:
curl --location --request GET 'localhost:3000/users/12345'
Наш задњи крај би требало да пошаље одговор GET requested for id 12345
.
Што се тиче POST
инг:
curl --location --request POST 'localhost:3000/users' --data-raw ''
Овај и сви други типови захтева за које смо направили скелете изгледаће прилично слично.
У овом чланку започели смо са креирањем РЕСТ АПИ-ја конфигурисањем пројекта од нуле и роњењем у основе Екпресс.јс оквира. Затим смо направили први корак ка савладавању ТипеСцрипт-а градећи образац са UsersRoutesConfig
продужавајући CommonRoutesConfig
, образац који ћемо поново користити за следећи чланак из ове серије. Завршили смо конфигурисањем наших app.ts
улазна тачка за коришћење наших нових рута и package.json
са скриптама за изградњу и покретање наше апликације.
Али чак су и основе РЕСТ АПИ-ја направљене помоћу Екпресс.јс и ТипеСцрипт прилично укључене. У следећи део ове серије фокусирамо се на стварање одговарајућих контролера за корисничке ресурсе и истражујемо неке корисне обрасце за услуге, међуопреме, контролере и моделе.
јавасцрипт добија датум у милисекундама
Комплетан пројекат је доступан на ГитХуб-у , а код на крају овог чланка налази се у toptal-article-01
грана.
Апсолутно! Веома је уобичајено да популарни нпм пакети (укључујући Екпресс.јс) имају одговарајуће датотеке дефиниције типа ТипеСцрипт. Ово важи за сам Ноде.јс, плус укључене поткомпоненте попут његовог пакета за отклањање грешака.
Да. Ноде.јс се сам може користити за креирање РЕСТ АПИ-ја спремних за производњу, а постоји и неколико популарних оквира попут Екпресс.јс за смањење неизбежног бојлера.
Не, није тешко започети учење ТипеСцрипт-а онима који имају модерну ЈаваСцрипт позадину. Још је лакше онима који имају искуства у објектно оријентисаном програмирању. Али савладавање свих ТипеСцрипт-ових нијанси и најбољих пракси захтева време, као и свака вештина.
Зависи од пројекта, али се дефинитивно препоручује за програмирање Ноде.јс. То је изражајнији језик за моделирање стварних домена проблема на задњем крају. То чини код читљивијим и смањује потенцијал за грешке.
ТипеСцрипт се користи свуда где се ЈаваСцрипт налази, али је посебно погодан за веће апликације. Користи ЈаваСцрипт као основу, додајући статичко куцање и много бољу подршку за парадигму објектно оријентисаног програмирања (ООП). Ово заузврат подржава напредније искуство у развоју и отклањању грешака.