การสร้าง Node.js/TypeScript REST API ส่วนที่ 1: Express.js

เผยแพร่แล้ว: 2022-03-11

ฉันจะเขียน REST API ใน Node.js ได้อย่างไร

เมื่อสร้างแบ็คเอนด์สำหรับ REST API Express.js มักจะเป็นตัวเลือกแรกในบรรดาเฟรมเวิร์ก Node.js แม้ว่าจะสนับสนุนการสร้าง HTML และเทมเพลตแบบคงที่ แต่ในชุดนี้ เราจะเน้นที่การพัฒนาส่วนหลังโดยใช้ TypeScript REST API ที่เป็นผลลัพธ์จะเป็นหนึ่งที่เฟรมเวิร์กส่วนหน้าหรือบริการแบ็คเอนด์ภายนอกจะสามารถสืบค้นได้

คุณจะต้องการ:

  • ความรู้พื้นฐานของ JavaScript และ TypeScript
  • ความรู้พื้นฐานของ Node.js
  • ความรู้พื้นฐานเกี่ยวกับสถาปัตยกรรม REST (อ้างอิงส่วนนี้ของบทความ REST API ก่อนหน้าของฉัน หากจำเป็น)
  • การติดตั้ง Node.js ที่พร้อมใช้งาน (ควรเป็นเวอร์ชัน 14+)

ในเทอร์มินัล (หรือพรอมต์คำสั่ง) เราจะสร้างโฟลเดอร์สำหรับโครงการ จากโฟลเดอร์นั้น ให้รัน npm init นั่นจะสร้างไฟล์โปรเจ็กต์ Node.js พื้นฐานที่เราต้องการ

ต่อไป เราจะเพิ่มเฟรมเวิร์ก Express.js และไลบรารี่ที่เป็นประโยชน์:

 npm i express debug winston express-winston cors

มีเหตุผลที่ดีที่ไลบรารีเหล่านี้เป็นรายการโปรดของนักพัฒนา Node.js:

  • debug เป็นโมดูลที่เราจะใช้เพื่อหลีกเลี่ยงการเรียก console.log() ในขณะที่พัฒนาแอปพลิเคชันของเรา ด้วยวิธีนี้ เราสามารถกรองคำสั่งแก้ไขข้อบกพร่องระหว่างการแก้ไขปัญหาได้อย่างง่ายดาย นอกจากนี้ยังสามารถปิดได้ทั้งหมดในการผลิต แทนที่จะต้องนำออกด้วยตนเอง
  • winston มีหน้าที่รับผิดชอบในการบันทึกคำขอไปยัง API ของเราและการตอบกลับ (และข้อผิดพลาด) ที่ส่งคืน express-winston รวมโดยตรงกับ Express.js เพื่อให้รหัสการบันทึก winston ที่เกี่ยวข้องกับ API มาตรฐานทั้งหมดเสร็จสิ้นแล้ว
  • cors เป็นส่วนหนึ่งของมิดเดิลแวร์ Express.js ที่ช่วยให้เราเปิดใช้งานการแชร์ทรัพยากรข้ามต้นทาง หากไม่มีสิ่งนี้ API ของเราจะใช้งานได้จากส่วนหน้าที่ให้บริการจากโดเมนย่อยเดียวกันกับส่วนหลังของเราเท่านั้น

แบ็กเอนด์ของเราใช้แพ็คเกจเหล่านี้เมื่อทำงาน แต่เรายังต้องติดตั้งการพึ่งพา การพัฒนา บางอย่างสำหรับการกำหนดค่า TypeScript ของเรา สำหรับสิ่งนั้น เราจะดำเนินการ:

 npm i --save-dev @types/cors @types/express @types/debug source-map-support tslint typescript

การขึ้นต่อกันเหล่านี้จำเป็นสำหรับการเปิดใช้งาน TypeScript สำหรับโค้ดของแอปเอง ควบคู่ไปกับประเภทที่ใช้โดย Express.js และการขึ้นต่อกันอื่นๆ ซึ่งจะช่วยประหยัดเวลาได้มากเมื่อเราใช้ IDE เช่น WebStorm หรือ VSCode โดยอนุญาตให้เราใช้วิธีการทำงานบางอย่างโดยอัตโนมัติขณะเขียนโค้ด

การขึ้นต่อกันขั้นสุดท้ายใน 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" }

ตอนนี้เราได้ติดตั้งการพึ่งพาที่จำเป็นทั้งหมดแล้ว มาเริ่มสร้างโค้ดของเราเองกันเถอะ!

โครงสร้างโปรเจ็กต์ TypeScript REST API

สำหรับบทช่วยสอนนี้ เราจะสร้างไฟล์เพียงสามไฟล์:

  1. ./app.ts
  2. ./common/common.routes.config.ts
  3. ./users/users.routes.config.ts

แนวคิดเบื้องหลังสองโฟลเดอร์ของโครงสร้างโครงการ ( common และ users ) คือการมีแต่ละโมดูลที่มีหน้าที่รับผิดชอบของตนเอง ในแง่นี้ ในที่สุดเราจะมีสิ่งต่อไปนี้บางส่วนหรือทั้งหมดสำหรับแต่ละโมดูล:

  • การกำหนดค่าเส้นทาง เพื่อกำหนดคำขอ API ของเราสามารถจัดการได้
  • บริการ สำหรับงานต่างๆ เช่น การเชื่อมต่อกับโมเดลฐานข้อมูล การสอบถาม หรือการเชื่อมต่อกับบริการภายนอกที่ร้องขอโดยเฉพาะ
  • มิดเดิลแวร์ สำหรับเรียกใช้การตรวจสอบคำขอเฉพาะก่อนที่ตัวควบคุมสุดท้ายของเส้นทางจะจัดการกับข้อมูลเฉพาะ
  • แบบจำลอง สำหรับกำหนดแบบจำลองข้อมูลที่ตรงกับสคีมาฐานข้อมูลที่กำหนด เพื่ออำนวยความสะดวกในการจัดเก็บและเรียกค้นข้อมูล
  • ตัวควบคุม สำหรับแยกการกำหนดค่าเส้นทางออกจากโค้ดที่สุดท้าย (หลังจากมิดเดิลแวร์ใดๆ) ประมวลผลคำขอเส้นทาง เรียกใช้ฟังก์ชันบริการด้านบนหากจำเป็น และให้การตอบสนองต่อไคลเอ็นต์

โครงสร้างโฟลเดอร์นี้มีการออกแบบ REST API พื้นฐาน ซึ่งเป็นจุดเริ่มต้นเบื้องต้นสำหรับชุดการสอนนี้ที่เหลือ และเพียงพอสำหรับเริ่มฝึก

ไฟล์เส้นทางทั่วไปใน TypeScript

ในโฟลเดอร์ 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; } }

วิธีที่เรากำลังสร้างเส้นทางที่นี่เป็นทางเลือก แต่เนื่องจากเรากำลังทำงานกับ TypeScript สถานการณ์เส้นทางของเราจึงเป็นโอกาสในการฝึกฝนการใช้การสืบทอดด้วยคีย์เวิร์ดที่ extends ดังที่เราจะได้เห็นในเร็วๆ นี้ ในโปรเจ็กต์นี้ ไฟล์เส้นทางทั้งหมดมีลักษณะการทำงานเหมือนกัน: มีชื่อ (ซึ่งเราจะใช้เพื่อจุดประสงค์ในการดีบัก) และเข้าถึงออบเจกต์ Express.js 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 หลัก) และชื่อ UsersRoutes ไปยัง Constructor ของ CommonRoutesConfig

ตัวอย่างนี้ค่อนข้างง่าย แต่เมื่อทำการปรับขนาดเพื่อสร้างไฟล์เส้นทางหลายไฟล์ สิ่งนี้จะช่วยเราหลีกเลี่ยงโค้ดที่ซ้ำกัน

สมมติว่าเราต้องการเพิ่มคุณสมบัติใหม่ในไฟล์นี้ เช่น การบันทึก เราสามารถเพิ่มฟิลด์ที่จำเป็นให้กับคลาส CommonRoutesConfig จากนั้นเส้นทางทั้งหมดที่ขยาย CommonRoutesConfig จะสามารถเข้าถึงได้

การใช้ฟังก์ชันนามธรรมของ TypeScript เพื่อการทำงานที่คล้ายคลึงกันในชั้นเรียนต่างๆ

จะเกิดอะไรขึ้นถ้าเราต้องการมีฟังก์ชันการทำงานที่ คล้ายคลึงกัน ระหว่างคลาสเหล่านี้ (เช่น การกำหนดค่าจุดปลาย API) แต่นั่นต้องการการใช้งานที่แตกต่างกันสำหรับแต่ละคลาส ทางเลือกหนึ่งคือการใช้คุณลักษณะ TypeScript ที่เรียกว่า abstraction

มาสร้างฟังก์ชันนามธรรมง่ายๆ ที่คลาส UsersRoutes (และคลาสการกำหนดเส้นทางในอนาคต) จะสืบทอดจาก CommonRoutesConfig สมมติว่าเราต้องการบังคับทุกเส้นทางให้มี configureRoutes() นั่นคือที่ที่เราจะประกาศจุดสิ้นสุดของทรัพยากรของคลาสการกำหนดเส้นทางแต่ละคลาส

ในการดำเนินการนี้ เราจะเพิ่มข้อมูลด่วนสามอย่างใน common.routes.config.ts :

  1. คีย์เวิร์ด abstract ใน class ของเรา เพื่อเปิดใช้งาน abstraction สำหรับคลาสนี้
  2. การประกาศฟังก์ชันใหม่ที่ส่วนท้ายของคลาส abstract configureRoutes(): express.Application; . การดำเนินการนี้บังคับคลาสใดๆ ที่ขยาย CommonRoutesConfig เพื่อจัดเตรียมการใช้งานที่ตรงกับลายเซ็นนั้น—หากไม่เป็นเช่นนั้น คอมไพเลอร์ TypeScript จะส่งข้อผิดพลาด
  3. การเรียก 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() เราส่งผ่านแอปพลิเคชันและชื่อเส้นทางของเราไปยัง Constructor ของ CommonRoutesConfig ซึ่งในสถานการณ์นี้คือ UsersRoutes ( super() ในทางกลับกันจะเรียกการใช้งานของเรา configureRoutes() .)

การกำหนดค่าเส้นทาง Express.js ของผู้ใช้ปลายทาง

ฟังก์ชัน configureRoutes() คือที่ที่เราจะสร้างปลายทางสำหรับผู้ใช้ REST API ของเรา เราจะใช้แอปพลิเคชันและฟังก์ชันเส้นทางจาก Express.js ที่นั่น

แนวคิดในการใช้ app.route() คือการหลีกเลี่ยงการทำซ้ำโค้ด ซึ่งง่าย เนื่องจากเรากำลังสร้าง REST API ด้วยทรัพยากรที่กำหนดไว้อย่างดี ทรัพยากรหลักสำหรับบทช่วยสอนนี้คือ ผู้ใช้ เรามีสองกรณีในสถานการณ์นี้:

  • เมื่อผู้เรียก API ต้องการสร้างผู้ใช้ใหม่หรือแสดงรายการผู้ใช้ที่มีอยู่ทั้งหมด ปลายทางควรมี users ที่ส่วนท้ายของเส้นทางที่ร้องขอในขั้นต้น (เราจะไม่เข้าสู่การกรองข้อความค้นหา การแบ่งหน้า หรือข้อความค้นหาอื่นๆ ในบทความนี้)
  • เมื่อผู้เรียกต้องการทำบางสิ่งเฉพาะกับบันทึกผู้ใช้เฉพาะ เส้นทางทรัพยากรของคำขอจะเป็นไปตามรูปแบบ users/:userId

วิธีที่ .route() ทำงานใน Express.js ทำให้เราจัดการกับกริยา HTTP ได้ด้วยการโยงที่สง่างาม นี่เป็นเพราะ .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; }

โค้ดด้านบนช่วยให้ไคลเอ็นต์ REST API เรียกปลายทาง users ของเราด้วย POST หรือ GET ในทำนองเดียวกัน ช่วยให้ลูกค้าเรียกปลายทางของเรา /users/:userId ด้วยคำขอ GET , PUT , PATCH หรือ DELETE

แต่สำหรับ /users/:userId เราได้เพิ่มมิดเดิลแวร์ทั่วไปโดยใช้ฟังก์ชัน all() ซึ่งจะทำงานก่อนฟังก์ชัน get() , put() , patch() หรือ delete() ฟังก์ชันนี้จะเป็นประโยชน์เมื่อ (ต่อมาในชุด) เราสร้างเส้นทางที่มีขึ้นเพื่อให้เข้าถึงได้โดยผู้ใช้ที่ได้รับการรับรองความถูกต้องเท่านั้น

คุณอาจสังเกตเห็นว่าในฟังก์ชัน .all() ของเรา เช่นเดียวกับมิดเดิลแวร์ใดๆ เรามีฟิลด์สามประเภท: Request , Response และ NextFunction

  • คำขอเป็นวิธีที่ Express.js แสดงถึงคำขอ HTTP ที่จะจัดการ ประเภทนี้อัพเกรดและขยายประเภทคำขอ Node.js ดั้งเดิม
  • การตอบสนองก็เช่นเดียวกันกับวิธีที่ Express.js แสดงถึงการตอบสนอง HTTP ซึ่งขยายประเภทการตอบสนองของ Node.js ดั้งเดิมอีกครั้ง
  • สิ่งที่สำคัญไม่น้อยไปกว่านั้นคือ NextFunction ทำหน้าที่เป็นฟังก์ชันเรียกกลับ ซึ่งช่วยให้สามารถควบคุมผ่านฟังก์ชันมิดเดิลแวร์อื่นๆ ได้ ระหว่างทาง มิดเดิลแวร์ทั้งหมดจะแชร์คำขอและอ็อบเจ็กต์การตอบกลับเดียวกันก่อนที่คอนโทรลเลอร์จะส่งการตอบกลับกลับไปยังผู้ร้องขอในที่สุด

ไฟล์จุดเข้าใช้งาน Node.js ของเรา app.ts

ตอนนี้เราได้กำหนดค่าโครงร่างเส้นทางพื้นฐานแล้ว เราจะเริ่มกำหนดค่าจุดเข้าใช้งานของแอปพลิเคชัน มาสร้างไฟล์ app.ts ที่รูทของโฟลเดอร์โปรเจ็กต์ของเรา และเริ่มด้วยรหัสนี้:

 import express from 'express'; import * as http from 'http'; 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 เป็นโมดูล Node.js-native จำเป็นต้องเริ่มต้นแอปพลิเคชัน Express.js ของเรา
  • body-parser เป็นมิดเดิลแวร์ที่มาพร้อมกับ Express.js มันแยกวิเคราะห์คำขอ (ในกรณีของเราเป็น JSON) ก่อนที่การควบคุมจะถูกส่งไปยังตัวจัดการคำขอของเราเอง

เมื่อเรานำเข้าไฟล์แล้ว เราจะเริ่มประกาศตัวแปรที่เราต้องการใช้:

 const app: express.Application = express(); const server: http.Server = http.createServer(app); const port = 3000; const routes: Array<CommonRoutesConfig> = []; const debugLog: debug.IDebugger = debug('app');

ฟังก์ชัน express() จะคืนค่าออบเจ็กต์แอปพลิเคชัน Express.js หลักที่เราจะส่งต่อทั่วทั้งโค้ดของเรา เริ่มต้นด้วยการเพิ่มลงในออบเจ็กต์ http.Server (เราจะต้องเริ่ม http.Server หลังจากกำหนดค่า express.Application ของเราแล้ว)

เราจะฟังบนพอร์ต 3000 ซึ่ง TypeScript จะอนุมานโดยอัตโนมัติว่าเป็น Number แทนที่จะเป็นพอร์ตมาตรฐาน 80 (HTTP) หรือ 443 (HTTPS) เนื่องจากโดยปกติแล้วจะใช้สำหรับส่วนหน้าของแอป

ทำไมต้องพอร์ต 3000?

ไม่มีกฎว่าพอร์ตควรเป็น 3000—หากไม่ระบุ พอร์ตใดๆ จะถูกกำหนด—แต่จะใช้ 3000 ตลอดตัวอย่างเอกสารสำหรับทั้ง Node.js และ Express.js ดังนั้นเราจึงดำเนินการตามธรรมเนียมนี้ต่อไป

Node.js สามารถแชร์พอร์ตกับ Front End ได้หรือไม่?

เรายังคงสามารถเรียกใช้ภายในพอร์ตที่กำหนดเองได้ แม้ว่าเราต้องการให้แบ็คเอนด์ของเราตอบสนองต่อคำขอบนพอร์ตมาตรฐานก็ตาม สิ่งนี้จะต้องใช้พร็อกซีย้อนกลับเพื่อรับคำขอบนพอร์ต 80 หรือ 443 ที่มีโดเมนเฉพาะหรือโดเมนย่อย จากนั้นจะเปลี่ยนเส้นทางไปยังพอร์ตภายใน 3000 ของเรา

อาร์เรย์ routes จะติดตามไฟล์เส้นทางของเราเพื่อจุดประสงค์ในการดีบัก ดังที่เราจะเห็นด้านล่าง

สุดท้าย debugLog จะจบลงเป็นฟังก์ชันที่คล้ายกับ console.log แต่ดีกว่า: ปรับแต่งได้ง่ายกว่าเพราะกำหนดขอบเขตโดยอัตโนมัติสำหรับสิ่งที่เราต้องการเรียกบริบทของไฟล์/โมดูล (ในกรณีนี้ เราเรียกมันว่า "แอพ" เมื่อเราส่งมันในสตริงไปยังตัวสร้าง debug() )

ตอนนี้ เราพร้อมที่จะกำหนดค่าโมดูลมิดเดิลแวร์ Express.js ทั้งหมดและเส้นทางของ API ของเราแล้ว:

 // here we are adding middleware to parse all incoming requests as JSON app.use(express.json()); // here we are adding middleware to allow cross-origin requests app.use(cors()); // here we are preparing the expressWinston logging middleware configuration, // which will automatically log all HTTP requests handled by Express.js const loggerOptions: expressWinston.LoggerOptions = { transports: [new winston.transports.Console()], format: winston.format.combine( winston.format.json(), winston.format.prettyPrint(), winston.format.colorize({ all: true }) ), }; if (!process.env.DEBUG) { loggerOptions.meta = false; // when not debugging, log requests as one-liners } // initialize the logger with the above configuration app.use(expressWinston.logger(loggerOptions)); // 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)); // this is a simple route to make sure everything is working properly const runningMessage = `Server running at http://localhost:${port}`; app.get('/', (req: express.Request, res: express.Response) => { res.status(200).send(runningMessage) });

expressWinston.logger กับ Express.js โดยจะบันทึกรายละเอียดโดยอัตโนมัติ—ผ่านโครงสร้างพื้นฐานเดียวกันกับการ debug —สำหรับทุกคำขอที่เสร็จสิ้น ตัวเลือกที่เราได้ส่งผ่านจะจัดรูปแบบและปรับสีเอาต์พุตเทอร์มินัลที่เกี่ยวข้อง พร้อมการบันทึกอย่างละเอียดมากขึ้น (ค่าเริ่มต้น) เมื่อเราอยู่ในโหมดแก้ไขข้อบกพร่อง

โปรดทราบว่าเราต้องกำหนดเส้นทางของเราหลังจากตั้งค่า expressWinston.logger

สุดท้ายและที่สำคัญที่สุด:

 server.listen(port, () => { routes.forEach((route: CommonRoutesConfig) => { debugLog(`Routes configured for ${route.getName()}`); }); // our only exception to avoiding console.log(), because we // always want to know when the server is done starting up console.log(runningMessage); });

นี่เป็นการเริ่มต้นเซิร์ฟเวอร์ของเราจริงๆ เมื่อเริ่มต้น Node.js จะเรียกใช้ฟังก์ชันการโทรกลับของเรา ซึ่งในโหมดแก้ไขจุดบกพร่องจะรายงานชื่อของเส้นทางทั้งหมดที่เรากำหนดค่าไว้ จนถึงตอนนี้ มีเพียง UsersRoutes หลังจากนั้น การโทรกลับจะแจ้งเราว่าส่วนหลังของเราพร้อมที่จะรับคำขอ แม้จะทำงานในโหมดใช้งานจริงก็ตาม

กำลังอัปเดต package.json เป็น Transpile TypeScript เป็น JavaScript และเรียกใช้ App

ตอนนี้เรามีโครงร่างของเราที่พร้อมใช้งานแล้ว อันดับแรก เราต้องกำหนดค่าสำเร็จรูปบางอย่างเพื่อเปิดใช้งานการทรานสพิล TypeScript มาเพิ่มไฟล์ tsconfig.json ในรูทโปรเจ็กต์กันเถอะ:

 { "compilerOptions": { "target": "es2016", "module": "commonjs", "outDir": "./dist", "strict": true, "esModuleInterop": true, "inlineSourceMap": true } }

จากนั้นเราเพียงแค่ต้องเพิ่มส่วนสุดท้ายใน package.json ในรูปแบบของสคริปต์ต่อไปนี้:

 "scripts": { "start": "tsc && node --unhandled-rejections=strict ./dist/app.js", "debug": "export DEBUG=* && npm run start", "test": "echo \"Error: no test specified\" && exit 1" },

สคริปต์ test คือตัวยึดตำแหน่งที่เราจะแทนที่ในภายหลังในซีรีส์

tsc ในสคริปต์ start เป็นของ TypeScript มีหน้าที่แปลงรหัส TypeScript ของเราเป็น JavaScript ซึ่งจะส่งออกไปยังโฟลเดอร์ dist จากนั้นเราก็เรียกใช้เวอร์ชันที่สร้างขึ้นด้วย node ./dist/app.js

เราส่งผ่าน --unhandled-rejections=strict ไปยัง Node.js (ถึงแม้จะใช้ Node.js v16+) เพราะในทางปฏิบัติ การดีบักโดยใช้วิธีการ "แครชและแสดงสแต็ก" แบบตรงไปตรงมานั้นตรงไปตรงมามากกว่าการบันทึกไฟล์ด้วยวัตถุ expressWinston.errorLogger สิ่งนี้มักเกิดขึ้นจริงแม้ในเวอร์ชันที่ใช้งานจริง โดยที่การปล่อยให้ Node.js ทำงานต่อไปแม้ว่าจะมีการปฏิเสธที่ไม่สามารถจัดการได้ มีแนวโน้มว่าจะทำให้เซิร์ฟเวอร์อยู่ในสถานะที่ไม่คาดคิด ทำให้เกิดจุดบกพร่องเพิ่มเติม (และซับซ้อนกว่า) ได้

สคริปต์การ debug เรียกสคริปต์ start แต่กำหนดตัวแปรสภาพแวดล้อม DEBUG ก่อน สิ่งนี้มีผลในการเปิดใช้งานคำสั่ง debugLog() ทั้งหมดของเรา (รวมถึงคำสั่งที่คล้ายกันจาก Express.js เอง ซึ่งใช้โมดูลการ debug แบบเดียวกับที่เราทำ) เพื่อส่งออกรายละเอียดที่เป็นประโยชน์ไปยังเทอร์มินัล—รายละเอียดที่ (สะดวก) ซ่อนไว้เมื่อทำงาน เซิร์ฟเวอร์ในโหมดใช้งานจริงที่มีการ npm start มาตรฐาน

ลองรัน npm run debug ด้วยตัวเอง และหลังจากนั้น ให้เปรียบเทียบกับ npm start เพื่อดูว่าคอนโซลเอาต์พุตเปลี่ยนแปลงไปอย่างไร

เคล็ดลับ: คุณสามารถจำกัดเอาต์พุตการดีบักให้กับคำสั่ง debugLog() ของไฟล์ app.ts ของเราได้โดยใช้ DEBUG=app แทน DEBUG=* โดยทั่วไปแล้ว โมดูล debug จะค่อนข้างยืดหยุ่น และฟีเจอร์นี้ก็ไม่มีข้อยกเว้น

ผู้ใช้ Windows อาจจำเป็นต้องเปลี่ยนการ export เป็น SET เนื่องจาก export เป็นวิธีการทำงานบน Mac และ Linux หากโปรเจ็กต์ของคุณต้องรองรับสภาพแวดล้อมการพัฒนาที่หลากหลาย แพ็คเกจ cross-env จะให้โซลูชันที่ตรงไปตรงมาที่นี่

ทดสอบ Live Express.js Back End

ด้วย npm run debug npm หรือ npm start ยังคงดำเนินต่อไป REST API ของเราจะพร้อมสำหรับการร้องขอบริการบนพอร์ต 3000 ณ จุดนี้ เราสามารถใช้ cURL, Postman, Insomnia ฯลฯ เพื่อทดสอบส่วนหลัง

เนื่องจากเราสร้างแค่โครงกระดูกสำหรับทรัพยากรผู้ใช้ เราจึงสามารถส่งคำขอโดยไม่ต้องมีเนื้อหาเพื่อดูว่าทุกอย่างทำงานตามที่คาดไว้ ตัวอย่างเช่น:

 curl --request GET 'localhost:3000/users/12345'

แบ็คเอนด์ของเราควรส่งคำตอบที่ GET requested for id 12345

สำหรับ POST ing:

 curl --request POST 'localhost:3000/users' \ --data-raw ''

คำขอนี้และคำขอประเภทอื่นๆ ทั้งหมดที่เราสร้างโครงกระดูกจะมีลักษณะคล้ายกันมาก

เตรียมพร้อมสำหรับการพัฒนา Rapid Node.js REST API ด้วย TypeScript

ในบทความนี้ เราเริ่มสร้าง REST API โดยกำหนดค่าโปรเจ็กต์ตั้งแต่ต้นและเจาะลึกถึงพื้นฐานของเฟรมเวิร์ก Express.js จากนั้น เราเริ่มก้าวแรกสู่การเรียนรู้ TypeScript โดยการสร้างรูปแบบด้วย UsersRoutesConfig ขยาย CommonRoutesConfig ซึ่งเป็นรูปแบบที่เราจะนำมาใช้ใหม่ในบทความถัดไปในชุดนี้ เราเสร็จสิ้นโดยกำหนดค่าจุดเริ่มต้น app.ts เพื่อใช้เส้นทางใหม่และ package.json พร้อมสคริปต์เพื่อสร้างและเรียกใช้แอปพลิเคชันของเรา

แต่แม้กระทั่งพื้นฐานของ REST API ที่สร้างด้วย Express.js และ TypeScript ก็ค่อนข้างเกี่ยวข้อง ในส่วนถัดไปของชุดข้อมูลนี้ เรามุ่งเน้นที่การสร้างตัวควบคุมที่เหมาะสมสำหรับทรัพยากรผู้ใช้และเจาะลึกรูปแบบที่เป็นประโยชน์สำหรับบริการ มิดเดิลแวร์ ตัวควบคุม และรุ่นต่างๆ

โครงการฉบับเต็มมีอยู่ใน GitHub และรหัส ณ จุดสิ้นสุดของบทความนี้อยู่ใน toptal-article-01