本文最初发布在Okta开发人员博客上 。 感谢您支持使SitePoint成为可能的合作伙伴。
自1995年以来,无论是否喜欢,JavaScript一直在帮助开发人员为Internet提供支持。那时,JavaScript的使用已从小型用户体验增强功能发展为使用服务器上的Node.js和客户端上的许多框架之一的复杂的全栈应用程序。例如Angular,React或Vue。
如今, 大规模构建JavaScript应用程序仍然是一个挑战。 越来越多的团队开始使用TypeScript来补充其JavaScript项目。
Node.js服务器应用程序也可以从使用TypeScript中受益。 本教程的目的是向您展示如何使用TypeScript和Express构建新的Node.js应用程序。
作为一名Web开发人员,我很久以前就停止抵制JavaScript,并且逐渐意识到它的灵活性和普遍性。 ES2015及更高版本中添加的语言功能极大地提高了其实用性并减少了编写应用程序时的常见挫败感。
但是,较大JavaScript项目要求使用诸如ESLint之类的工具来捕获常见错误,并且需要更严格的规程以使代码库充满有用的测试。 与任何软件项目一样,包括同行评审过程在内的健康团队文化可以提高质量,并防止出现可能渗入项目的问题。
使用TypeScript的主要好处是可以在错误投入生产之前捕获更多错误,并使其更易于使用代码库。
TypeScript不是另一种语言。 它是JavaScript的灵活超集 ,具有描述可选数据类型的方式。 所有“标准”和有效JavaScript也是有效的TypeScript。 您可以根据需要拨打任意数量的电话。
将TypeScript编译器或TypeScript插件添加到您最喜欢的代码编辑器后,便会立即获得安全性和生产力上的好处。 TypeScript可以警告您拼写错误的函数和属性,检测将错误类型的参数或错误数量的参数传递给函数,并提供更智能的自动完成建议。
在吉他演奏家中,有一个每个人都应该理解的笑话。
问:“您需要几把吉他?”
答:“ n +1。总是一个。”
在本教程中,您将创建一个新的Node.js应用程序来跟踪吉他库存。 简而言之,本教程使用Node.js和Express , EJS和PostgreSQL在后端,在Vue , Materialize和Axios在前端, Okta进行帐户注册和授权,并使用TypeScript来管理JavaScript!
打开终端(Mac / Linux)或命令提示符(Windows)并键入以下命令:
node --version
如果出现错误,或者您拥有的Node.js版本低于版本8,则需要安装Node.js。 在Mac或Linux上,建议您首先安装nvm,然后使用nvm安装Node.js。 在Windows上,建议您使用Chocolatey 。
确保安装了最新版本的Node.js之后,为您的项目创建一个文件夹。
mkdir guitar-inventory
cd guitar-inventory
使用npm
初始化package.json
文件。
npm init -y
在此示例应用程序中, Express用于服务网页和实现API。 使用npm
安装依赖项。 使用以下命令将Express添加到您的项目中。
npm install express
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
接下来,在您选择的编辑器中打开项目。
如果您还没有喜欢的代码编辑器,请使用并推荐Visual Studio Code 。 VS Code对JavaScript和Node.js具有出色的支持,例如智能代码完成和调试,并且社区提供了大量免费的扩展库。
创建一个名为src
的文件夹。 在此文件夹中,创建一个名为index.js
的文件。 打开文件并添加以下JavaScript。
const express = require( "express" );
const app = express();
const port = 8080; // default port to listen
// define a route handler for the default home page
app.get( "/", ( req, res ) => {
res.send( "Hello world!" );
} );
// start the Express server
app.listen( port, () => {
console.log( `server started at http://localhost:${ port }` );
} );
接下来,更新package.json
以指导npm
如何运行您的应用程序。 将main
属性值更改为src/index.js
,然后将start
脚本添加到scripts
对象。
"main": "src/index.js",
"scripts": {
"start": "node .",
"test": "echo \"Error: no test specified\" && exit 1"
},
现在,可以从终端或命令行启动应用程序。
npm run start
如果一切顺利,您应该会看到此消息写入控制台。
server started at http://localhost:8080
启动浏览器并导航到http://localhost:8080
。 您应该看到文本“ Hello world!”。
注意:要停止Web应用程序,您可以返回到终端或命令提示符,然后按
CTRL+C
第一步是添加TypeScript编译器。 您可以使用--save-dev
标志将编译器安装为开发人员依赖项。
npm install --save-dev typescript
下一步是添加tsconfig.json
文件。 此文件指示TypeScript如何将TypeScript代码编译(转换)为纯JavaScript。
在项目的根文件夹中创建一个名为tsconfig.json
的文件,然后添加以下配置。
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": [
"node_modules/*"
]
}
},
"include": [
"src/**/*"
]
}
基于此tsconfig.json
文件,TypeScript编译器将(尝试)编译在src
文件夹中找到的以.ts
结尾的任何文件,并将结果存储在名为dist
的文件夹中。 Node.js使用CommonJS模块系统,因此module
设置的值为commonjs
。 此外,JavaScript的目标版本是ES6(ES2015),它与Node.js的现代版本兼容。
添加tslint
并创建一个tslint.json
文件来指示TypeScript如何tslint.json
代码也是一个好主意。 如果您不熟悉棉绒,它是一种代码分析工具,可以提醒您除语法问题之外的代码潜在问题。
将tslint
安装为开发人员依赖项。
npm install --save-dev typescript tslint
接下来,在名为tslint.json
的根文件夹中创建一个新文件,并添加以下配置。
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"trailing-comma": [ false ]
},
"rulesDirectory": []
}
接下来,更新您的package.json
以将main
更改为指向由TypeScript编译器创建的新dist
文件夹。 另外,在启动Node.js服务器之前,添加几个脚本来执行TSLint和TypeScript编译器。
"main": "dist/index.js",
"scripts": {
"prebuild": "tslint -c tslint.json -p tsconfig.json --fix",
"build": "tsc",
"prestart": "npm run build",
"start": "node .",
"test": "echo \"Error: no test specified\" && exit 1"
},
最后,将src/index.js
文件的扩展名从.js
更改为.ts
,即TypeScript扩展名,然后运行启动脚本。
npm run start
注意:无需使用
npm run build
即可启动TSLint和TypeScript编译器,而无需启动Node.js服务器。
不好了! 立即,您可能会看到一些类似这样的错误记录到控制台。
ERROR: /Users/reverentgeek/Projects/guitar-inventory/src/index.ts[12, 5]: Calls to 'console.log' are not allowed.
src/index.ts:1:17 - error TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i @types/node`.
1 const express = require( "express" );
~~~~~~~
src/index.ts:6:17 - error TS7006: Parameter 'req' implicitly has an 'any' type.
6 app.get( "/", ( req, res ) => {
~~~
您可能会看到的两个最常见的错误是语法错误和缺少类型信息。 TSLint认为使用console.log
是生产代码的问题。 最好的解决方案是用winston等日志记录框架替换console.log的使用。 现在,将以下注释添加到src/index.ts
以禁用该规则。
app.listen( port, () => {
// tslint:disable-next-line:no-console
console.log( `server started at http://localhost:${ port }` );
} );
TypeScript更喜欢使用import
模块语法而不是require
,因此您将从src/index.ts
的第一行src/index.ts
为:
const express = require( "express" );
至:
import express from "express";
为了帮助TypeScript开发人员,库作者和社区贡献者发布了称为TypeScript声明文件的配套库。 声明文件发布到DefinitelyTyped开源存储库中,或者有时在原始JavaScript库本身中找到。
更新您的项目,以便TypeScript可以使用Node.js和Express的类型声明。
npm install --save-dev @types/node @types/express
接下来,重新运行启动脚本并确认没有其他错误。
npm run start
您的Node.js应用程序有一个良好的开端,但可能看起来还不是最好的。 此步骤添加了Materialize (一个基于Google的Material Design的现代CSS框架)和Embedded JavaScript Templates (EJS)(一种用于ExpressHTML模板语言)。 Materialize和EJS是更好的UI的良好基础。
首先,安装EJS作为依赖项。
npm install ejs
接下来,在/src
下创建一个名为views
的新文件夹。 在/src/views
文件夹中,创建一个名为index.ejs
的文件。 将以下代码添加到/src/views/index.ejs
。
Guitar Inventory
Guitar Inventory
arrow_forwardGet started!
使用以下代码更新/src/index.ts
。
import express from "express";
import path from "path";
const app = express();
const port = 8080; // default port to listen
// Configure Express to use EJS
app.set( "views", path.join( __dirname, "views" ) );
app.set( "view engine", "ejs" );
// define a route handler for the default home page
app.get( "/", ( req, res ) => {
// render the index template
res.render( "index" );
} );
// start the express server
app.listen( port, () => {
// tslint:disable-next-line:no-console
console.log( `server started at http://localhost:${ port }` );
} );
TypeScript编译器完成生成JavaScript文件并将其复制到dist
文件夹的工作。 但是,它不会复制项目需要运行的其他类型的文件,例如EJS视图模板。 为此,创建一个将所有其他文件复制到dist
文件夹的构建脚本。
使用这些命令安装所需的模块和TypeScript声明。
npm install --save-dev ts-node shelljs fs-extra nodemon rimraf npm-run-all
npm install --save-dev @types/fs-extra @types/shelljs
这是您刚刚安装的模块的快速概述。
ts-node
。 用于直接运行TypeScript文件。 shelljs
。 用于执行外壳命令,例如复制文件和删除目录。 fs-extra
。 一个扩展Node.js文件系统( fs
)模块的模块,具有读取和写入JSON文件等功能。 rimraf
。 用于递归删除文件夹。 npm-run-all
。 用于顺序或并行执行多个npm
脚本。 nodemon
。 在开发环境中运行Node.js的便捷工具。 Nodemon监视文件中的更改,并在检测到更改时自动重新启动Node.js应用程序。 不再停止和重新启动Node.js! 在项目的根目录中创建一个名为tools
的新文件夹。 在tools
文件夹中创建一个名为copyAssets.ts
文件。 将以下代码复制到该文件中。
import * as shell from "shelljs";
// Copy all the view templates
shell.cp( "-R", "src/views", "dist/" );
将package.json
的scripts
更新为以下代码。
"scripts": {
"clean": "rimraf dist/*",
"copy-assets": "ts-node tools/copyAssets",
"lint": "tslint -c tslint.json -p tsconfig.json --fix",
"tsc": "tsc",
"build": "npm-run-all clean lint tsc copy-assets",
"dev:start": "npm-run-all build start",
"dev": "nodemon --watch src -e ts,ejs --exec npm run dev:start",
"start": "node .",
"test": "echo \"Error: no test specified\" && exit 1"
},
注意:如果您不熟悉使用
npm
脚本,那么它们对于任何Node.js项目都是非常强大和有用的。 脚本可以通过多种方式链接在一起。 将脚本链接在一起的一种方法是使用pre
和post
前缀。 例如,如果您有一个标记为start
脚本和另一个标记为prestart
脚本,则在终端上执行npm run start
将首先运行prestart
,并且只有在其成功完成后才start
运行。
现在运行该应用程序并导航到http:// localhost:8080。
npm run dev
主页开始看起来更好! 当然,“ 入门”按钮会导致令人失望的错误消息。 别担心! 该问题的解决方案即将推出!
Node.js应用程序通常使用环境变量进行配置。 但是,管理环境变量可能很麻烦。 dotenv是用于管理应用程序配置数据的流行模块。
安装dotenv
作为项目依赖项。
npm install dotenv
npm install --save-dev @types/dotenv
在项目的根文件夹中创建一个名为.env
的文件,并添加以下代码。
# Set to production when deploying to production
NODE_ENV=development
# Node.js server configuration
SERVER_PORT=8080
注意:使用诸如
git
类的源代码管理系统时, 请勿将.env
文件添加到源代码管理中。 每个环境都需要一个自定义的.env
文件。 建议您记录预计值.env
项目README或一个单独的文件.env.sample
文件。
现在,更新src/index.ts
以使用dotenv
来配置应用程序服务器端口值。
import dotenv from "dotenv";
import express from "express";
import path from "path";
// initialize configuration
dotenv.config();
// port is now available to the Node.js runtime
// as if it were an environment variable
const port = process.env.SERVER_PORT;
const app = express();
// Configure Express to use EJS
app.set( "views", path.join( __dirname, "views" ) );
app.set( "view engine", "ejs" );
// define a route handler for the default home page
app.get( "/", ( req, res ) => {
// render the index template
res.render( "index" );
} );
// start the express server
app.listen( port, () => {
// tslint:disable-next-line:no-console
console.log( `server started at http://localhost:${ port }` );
} );
随着项目的增长,您将使用.env
获取更多配置信息。
向任何应用程序添加用户注册和登录(身份验证)并不是一件容易的事。 好消息是Okta使此步骤非常容易。 首先,使用Okta创建一个免费的开发人员帐户。 首先,浏览至developer.okta.com ,然后单击“ 创建免费帐户”按钮,或单击“ 注册”按钮。
创建帐户后,单击顶部的“ 应用程序”链接,然后单击“ 添加应用程序” 。
接下来,选择一个Web应用程序 ,然后单击Next 。
输入您的应用程序的名称,例如Guitar Inventory 。 验证端口号与为本地Web应用程序配置的端口号相同。 然后,单击“完成”以完成应用程序的创建。
将以下代码复制并粘贴到您的.env
文件中。
# Okta configuration
OKTA_ORG_URL=https://{yourOktaDomain}
OKTA_CLIENT_ID={yourClientId}
OKTA_CLIENT_SECRET={yourClientSecret}
在Okta应用程序控制台中,单击新应用程序的“ 常规”选项卡,然后在页面底部附近找到标题为“客户端凭据”的部分。 复制客户ID和客户机密值,然后将其粘贴到您的.env
文件中,分别替换{yourClientId}
和{yourClientSecret}
。
Okta的一大功能是允许您的应用程序用户注册一个帐户。 默认情况下,此功能是禁用的,但是您可以轻松地启用它。 首先,单击“ 用户”菜单,然后选择“ 注册” 。
保护Node.js应用程序的最后一步是将Express配置为使用Okta OpenId Connect(OIDC)中间件 。
npm install @okta/oidc-middleware express-session
npm install --save-dev @types/express-session
接下来,更新您的.env
文件以添加HOST_URL
和SESSION_SECRET
值。 您可以将SESSION_SECRET
值更改为所需的任何字符串。
# Node.js server configuration
SERVER_PORT=8080
HOST_URL=http://localhost:8080
SESSION_SECRET=MySuperCoolAndAwesomeSecretForSigningSessionCookies
在src
下创建一个名为middleware
的文件夹。 将文件添加到名为sessionAuth.ts
的src/middleware
文件夹中。 将以下代码添加到src/middleware/sessionAuth.ts
。
import { ExpressOIDC } from "@okta/oidc-middleware";
import session from "express-session";
export const register = ( app: any ) => {
// Create the OIDC client
const oidc = new ExpressOIDC( {
client_id: process.env.OKTA_CLIENT_ID,
client_secret: process.env.OKTA_CLIENT_SECRET,
issuer: `${ process.env.OKTA_ORG_URL }/oauth2/default`,
redirect_uri: `${ process.env.HOST_URL }/authorization-code/callback`,
scope: "openid profile"
} );
// Configure Express to use authentication sessions
app.use( session( {
resave: true,
saveUninitialized: false,
secret: process.env.SESSION_SECRET
} ) );
// Configure Express to use the OIDC client router
app.use( oidc.router );
// add the OIDC client to the app.locals
app.locals.oidc = oidc;
};
此时,如果您使用的是VS Code之类的代码编辑器,则可能会看到TypeScript抱怨@okta/oidc-middleware
模块。 在撰写本文时,该模块还没有正式的TypeScript声明文件。 现在,在src
文件夹中创建一个名为global.d.ts
的文件,并添加以下代码。
declare module "@okta/oidc-middleware";
随着应用程序的增长,您将添加更多的路由。 最好在项目的一个区域中定义所有路线。 在src
下创建一个名为routes
的新文件夹。 将一个新文件添加到名为index.ts
src/routes
。 然后,将以下代码添加到该新文件中。
import * as express from "express";
export const register = ( app: express.Application ) => {
const oidc = app.locals.oidc;
// define a route handler for the default home page
app.get( "/", ( req: any, res ) => {
res.render( "index" );
} );
// define a secure route handler for the login page that redirects to /guitars
app.get( "/login", oidc.ensureAuthenticated(), ( req, res ) => {
res.redirect( "/guitars" );
} );
// define a route to handle logout
app.get( "/logout", ( req: any, res ) => {
req.logout();
res.redirect( "/" );
} );
// define a secure route handler for the guitars page
app.get( "/guitars", oidc.ensureAuthenticated(), ( req: any, res ) => {
res.render( "guitars" );
} );
};
接下来,更新src/index.ts
以使用sessionAuth
并routes
您创建的模块。
import dotenv from "dotenv";
import express from "express";
import path from "path";
import * as sessionAuth from "./middleware/sessionAuth";
import * as routes from "./routes";
// initialize configuration
dotenv.config();
// port is now available to the Node.js runtime
// as if it were an environment variable
const port = process.env.SERVER_PORT;
const app = express();
// Configure Express to use EJS
app.set( "views", path.join( __dirname, "views" ) );
app.set( "view engine", "ejs" );
// Configure session auth
sessionAuth.register( app );
// Configure routes
routes.register( app );
// start the express server
app.listen( port, () => {
// tslint:disable-next-line:no-console
console.log( `server started at http://localhost:${ port }` );
} );
接下来,在src/views/guitars.ejs
为吉他列表视图模板创建一个新文件,然后输入以下HTML。
Guitar Inventory
Guitar Inventory
Your future list of guitars!
最后,运行该应用程序。
npm run dev
注意:要验证身份验证是否按预期工作,请打开新的浏览器或使用专用/隐身浏览器窗口。
单击入门按钮。 如果一切顺利,请使用您的Okta帐户登录,Okta应该会自动将您重定向回“吉他列表”页面!
通过身份验证,您可以利用从Okta返回的用户配置文件信息。 OIDC中间件会自动将userContext
对象和isAuthenticated()
函数附加到每个请求。 此userContext
具有一个userinfo
属性,该属性包含类似于以下对象的信息。
{
sub: '00abc12defg3hij4k5l6',
name: 'First Last',
locale: 'en-US',
preferred_username: '[email protected]',
given_name: 'First',
family_name: 'Last',
zoneinfo: 'America/Los_Angeles',
updated_at: 1539283620
}
第一步是获取用户配置文件对象,并将其作为数据传递给视图。 使用以下代码更新src/routes/index.ts
。
import * as express from "express";
export const register = ( app: express.Application ) => {
const oidc = app.locals.oidc;
// define a route handler for the default home page
app.get( "/", ( req: any, res ) => {
const user = req.userContext ? req.userContext.userinfo : null;
res.render( "index", { isAuthenticated: req.isAuthenticated(), user } );
} );
// define a secure route handler for the login page that redirects to /guitars
app.get( "/login", oidc.ensureAuthenticated(), ( req, res ) => {
res.redirect( "/guitars" );
} );
// define a route to handle logout
app.get( "/logout", ( req: any, res ) => {
req.logout();
res.redirect( "/" );
} );
// define a secure route handler for the guitars page
app.get( "/guitars", oidc.ensureAuthenticated(), ( req: any, res ) => {
const user = req.userContext ? req.userContext.userinfo : null;
res.render( "guitars", { isAuthenticated: req.isAuthenticated(), user } );
} );
};
在src/views
下创建一个名为partials
的新文件夹。 在此文件夹中创建一个名为nav.ejs
的新文件。 将以下代码添加到src/views/partials/nav.ejs
。
修改src/views/index.ejs
和src/views/guitars.ejs
文件。 在标记之后,立即插入以下代码。
<% include partials/nav %>
完成这些更改后,您的应用程序现在在顶部具有一个导航菜单,该菜单会根据用户的登录状态进行更改。
下一步是将API添加到Guitar Inventory应用程序。 但是,在继续之前,您需要一种存储数据的方法。
本教程使用PostgreSQL 。 为了使事情变得简单,请使用Docker设置PostgreSQL实例。 如果尚未安装Docker,则可以按照安装指南进行操作 。
安装Docker之后,运行以下命令以下载最新的PostgreSQL容器。
docker pull postgres:latest
现在,运行此命令以创建PostgreSQL数据库服务器的实例。 随时更改管理员密码值。
docker run -d --name guitar-db -p 5432:5432 -e 'POSTGRES_PASSWORD=p@ssw0rd42' postgres
注意:如果已经在本地安装了PostgreSQL,则需要更改
-p
参数以将端口5432映射到与现有PostgreSQL实例不冲突的其他端口。
这是以前的Docker参数的快速说明。
-d
–这将以守护程序模式启动容器,因此它将在后台运行。 -name
–这为您的Docker容器提供了一个友好的名称,这对于停止和启动容器很有用。 -p
–这将主机(计算机)端口5432映射到容器的端口5432。默认情况下,PostgreSQL侦听TCP端口5432上的连接。 -e
–这将在容器中设置一个环境变量。 在此示例中,管理员密码为p@ssw0rd42
。 您可以将此值更改为所需的任何密码。 postgres
–此最终参数告诉Docker使用postgres映像。 注意:如果重新启动计算机,则可能需要重新启动Docker容器。 您可以使用
docker start guitar-db
命令执行此操作。
安装PostgreSQL客户端模块并使用以下命令键入声明。
npm install pg pg-promise
npm install --save-dev @types/pg
将以下设置添加到.env
文件的末尾。
# Postgres configuration
PGHOST=localhost
PGUSER=postgres
PGDATABASE=postgres
PGPASSWORD=p@ssw0rd42
PGPORT=5432
注意:如果您更改了数据库管理员密码,请确保在该文件中用该密码替换默认的p@ssw0rd42
。
您需要一个构建脚本来初始化PostgreSQL数据库。 该脚本应读取一个.pgsql
文件,并对本地数据库执行SQL命令。
在tools
文件夹中,创建两个文件: initdb.ts
和initdb.pgsql
。 将以下代码复制并粘贴到initdb.ts
。
import dotenv from "dotenv";
import fs from "fs-extra";
import { Client } from "pg";
const init = async () => {
// read environment variables
dotenv.config();
// create an instance of the PostgreSQL client
const client = new Client();
try {
// connect to the local database server
await client.connect();
// read the contents of the initdb.pgsql file
const sql = await fs.readFile( "./tools/initdb.pgsql", { encoding: "UTF-8" } );
// split the file into separate statements
const statements = sql.split( /;\s*$/m );
for ( const statement of statements ) {
if ( statement.length > 3 ) {
// execute each of the statements
await client.query( statement );
}
}
} catch ( err ) {
console.log( err );
throw err;
} finally {
// close the database client
await client.end();
}
};
init().then( () => {
console.log( "finished" );
} ).catch( () => {
console.log( "finished with errors" );
} );
接下来,将以下代码复制并粘贴到initdb.pgsql
。
-- Drops guitars table
DROP TABLE IF EXISTS guitars;
-- Creates guitars table
CREATE TABLE IF NOT EXISTS guitars (
id INT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY
, user_id varchar(50) NOT NULL
, brand varchar(50) NOT NULL
, model varchar(50) NOT NULL
, year smallint NULL
, color varchar(50) NULL
);
接下来,向package.json
添加一个新脚本。
"initdb": "ts-node tools/initdb",
现在,转到终端并运行新脚本。
npm run initdb
您应该在控制台上看到finished
的消息。 现在,您的数据库中guitars
一个名为guitars
的新表! 每当您想重置数据库时,只需重新运行脚本即可。
要完成API,您需要向Express添加新路线以创建,查询,更新和删除吉他。 首先,在src/routes
下创建一个名为api.ts
的新文件。 将以下代码添加到该文件。
import * as express from "express";
import pgPromise from "pg-promise";
export const register = ( app: express.Application ) => {
const oidc = app.locals.oidc;
const port = parseInt( process.env.PGPORT || "5432", 10 );
const config = {
database: process.env.PGDATABASE || "postgres",
host: process.env.PGHOST || "localhost",
port,
user: process.env.PGUSER || "postgres"
};
const pgp = pgPromise();
const db = pgp( config );
app.get( `/api/guitars/all`, oidc.ensureAuthenticated(), async ( req: any, res ) => {
try {
const userId = req.userContext.userinfo.sub;
const guitars = await db.any( `
SELECT
id
, brand
, model
, year
, color
FROM guitars
WHERE user_id = $[userId]
ORDER BY year, brand, model`, { userId } );
return res.json( guitars );
} catch ( err ) {
// tslint:disable-next-line:no-console
console.error(err);
res.json( { error: err.message || err } );
}
} );
app.get( `/api/guitars/total`, oidc.ensureAuthenticated(), async ( req: any, res ) => {
try {
const userId = req.userContext.userinfo.sub;
const total = await db.one( `
SELECT count(*) AS total
FROM guitars
WHERE user_id = $[userId]`, { userId }, ( data: { total: number } ) => {
return {
total: +data.total
};
} );
return res.json( total );
} catch ( err ) {
// tslint:disable-next-line:no-console
console.error(err);
res.json( { error: err.message || err } );
}
} );
app.get( `/api/guitars/find/:search`, oidc.ensureAuthenticated(), async ( req: any, res ) => {
try {
const userId = req.userContext.userinfo.sub;
const guitars = await db.any( `
SELECT
id
, brand
, model
, year
, color
FROM guitars
WHERE user_id = $[userId]
AND ( brand ILIKE $[search] OR model ILIKE $[search] )`,
{ userId, search: `%${ req.params.search }%` } );
return res.json( guitars );
} catch ( err ) {
// tslint:disable-next-line:no-console
console.error(err);
res.json( { error: err.message || err } );
}
} );
app.post( `/api/guitars/add`, oidc.ensureAuthenticated(), async ( req: any, res ) => {
try {
const userId = req.userContext.userinfo.sub;
const id = await db.one( `
INSERT INTO guitars( user_id, brand, model, year, color )
VALUES( $[userId], $[brand], $[model], $[year], $[color] )
RETURNING id;`,
{ userId, ...req.body } );
return res.json( { id } );
} catch ( err ) {
// tslint:disable-next-line:no-console
console.error(err);
res.json( { error: err.message || err } );
}
} );
app.post( `/api/guitars/update`, oidc.ensureAuthenticated(), async ( req: any, res ) => {
try {
const userId = req.userContext.userinfo.sub;
const id = await db.one( `
UPDATE guitars
SET brand = $[brand]
, model = $[model]
, year = $[year]
, color = $[color]
WHERE
id = $[id]
AND user_id = $[userId]
RETURNING
id;`,
{ userId, ...req.body } );
return res.json( { id } );
} catch ( err ) {
// tslint:disable-next-line:no-console
console.error(err);
res.json( { error: err.message || err } );
}
} );
app.delete( `/api/guitars/remove/:id`, oidc.ensureAuthenticated(), async ( req: any, res ) => {
try {
const userId = req.userContext.userinfo.sub;
const id = await db.result( `
DELETE
FROM guitars
WHERE user_id = $[userId]
AND id = $[id]`,
{ userId, id: req.params.id }, ( r ) => r.rowCount );
return res.json( { id } );
} catch ( err ) {
// tslint:disable-next-line:no-console
console.error(err);
res.json( { error: err.message || err } );
}
} );
};
更新src/routes/index.ts
以包括新的api
模块。
import * as express from "express";
import * as api from "./api";
export const register = ( app: express.Application ) => {
const oidc = app.locals.oidc;
// define a route handler for the default home page
app.get( "/", ( req: any, res ) => {
const user = req.userContext ? req.userContext.userinfo : null;
res.render( "index", { isAuthenticated: req.isAuthenticated(), user } );
} );
// define a secure route handler for the login page that redirects to /guitars
app.get( "/login", oidc.ensureAuthenticated(), ( req, res ) => {
res.redirect( "/guitars" );
} );
// define a route to handle logout
app.get( "/logout", ( req: any, res ) => {
req.logout();
res.redirect( "/" );
} );
// define a secure route handler for the guitars page
app.get( "/guitars", oidc.ensureAuthenticated(), ( req: any, res ) => {
const user = req.userContext ? req.userContext.userinfo : null;
res.render( "guitars", { isAuthenticated: req.isAuthenticated(), user } );
} );
api.register( app );
};
最后,更新src/index.ts
以在创建Express应用程序的行之后立即添加新的配置选项。 此代码使Express可以解析传入的JSON数据。
const app = express();
// Configure Express to parse incoming JSON data
app.use( express.json() );
API已准备就绪。 要完成该应用程序,您需要向前端添加一些代码以使用该API。 您还可以将TypeScript与前端代码一起使用。
该项目的最后一步使用Vue进行前端渲染,使用Axios进行对后端API的HTTP调用,并使用Parcel转换TypeScript并将所有依赖项捆绑到一个JavaScript文件中。
首先,使用以下命令在控制台上安装新的依赖项。
npm install axios vue materialize-css
npm install --save-dev parcel-bundler @types/axios @types/materialize-css @types/vue
在src
下新建一个名为public
文件夹。 在src/public
下创建一个名为js
的新文件夹。 在src/public/js
下创建一个名为main.ts
的文件,并添加以下代码。
import axios from "axios";
import * as M from "materialize-css";
import Vue from "vue";
// tslint:disable-next-line no-unused-expression
new Vue( {
computed: {
hazGuitars(): boolean {
return this.isLoading === false && this.guitars.length > 0;
},
noGuitars(): boolean {
return this.isLoading === false && this.guitars.length === 0;
}
},
data() {
return {
brand: "",
color: "",
guitars: [],
isLoading: true,
model: "",
selectedGuitar: "",
selectedGuitarId: 0,
year: ""
};
},
el: "#app",
methods: {
addGuitar() {
const guitar = {
brand: this.brand,
color: this.color,
model: this.model,
year: this.year
};
axios
.post( "/api/guitars/add", guitar )
.then( () => {
this.$refs.year.focus();
this.brand = "";
this.color = "";
this.model = "";
this.year = "";
this.loadGuitars();
} )
.catch( ( err: any ) => {
// tslint:disable-next-line:no-console
console.log( err );
} );
},
confirmDeleteGuitar( id: string ) {
const guitar = this.guitars.find( ( g ) => g.id === id );
this.selectedGuitar = `${ guitar.year } ${ guitar.brand } ${ guitar.model }`;
this.selectedGuitarId = guitar.id;
const dc = this.$refs.deleteConfirm;
const modal = M.Modal.init( dc );
modal.open();
},
deleteGuitar( id: string ) {
axios
.delete( `/api/guitars/remove/${ id }` )
.then( this.loadGuitars )
.catch( ( err: any ) => {
// tslint:disable-next-line:no-console
console.log( err );
} );
},
loadGuitars() {
axios
.get( "/api/guitars/all" )
.then( ( res: any ) => {
this.isLoading = false;
this.guitars = res.data;
} )
.catch( ( err: any ) => {
// tslint:disable-next-line:no-console
console.log( err );
} );
}
},
mounted() {
return this.loadGuitars();
}
} );
更新tsconfig.json
以从后端Node.js构建过程中排除src/public
文件夹。
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"noImplicitAny": true,
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"paths": {
"*": [
"node_modules/*"
]
}
},
"include": [
"src/**/*"
],
"exclude": [
"src/public"
]
}
在src/public/js
下创建一个新的tsconfig.json
文件,并添加以下代码。 此TypeScript配置用于编译main.ts
以在浏览器中使用。
{
"compilerOptions": {
"lib": [
"es6",
"dom"
],
"noImplicitAny": true,
"allowJs": true,
"target": "es5",
"strict": true,
"module": "es6",
"moduleResolution": "node",
"outDir": "../../../dist/public/js",
"sourceMap": true
}
}
接下来,更新src/index.ts
配置快递从提供静态文件public
文件夹。 在将Express配置为使用EJS
的代码之后添加此行。
...
// Configure Express to use EJS
app.set( "views", path.join( __dirname, "views" ) );
app.set( "view engine", "ejs" );
// Configure Express to serve static files in the public folder
app.use( express.static( path.join( __dirname, "public" ) ) );
更新src/views/guitars.ejs
以添加Vue应用程序模板和对js/main.js
文件的引用。
Guitar Inventory
<% include partials/nav %>
Guitar list
Year
Brand
Model
Color
No guitars yet!
Add a guitar
最后,更新package.json
添加新的parcel
脚本,更新build
脚本,并添加新的alias
为Vue的部分。 alias
部分将Parcel指向正确的Vue文件,以与src/public/js/main.ts
捆绑在一起。
"scripts": {
"clean": "rimraf dist/*",
"copy-assets": "ts-node tools/copyAssets",
"lint": "tslint -c tslint.json -p tsconfig.json --fix",
"tsc": "tsc",
"parcel": "parcel build src/public/js/main.ts -d dist/public/js",
"build": "npm-run-all clean lint tsc copy-assets parcel",
"dev:start": "npm-run-all build start",
"dev": "nodemon --watch src -e ts,ejs --exec npm run dev:start",
"start": "node .",
"initdb": "ts-node tools/initdb",
"test": "echo \"Error: no test specified\" && exit 1"
},
"alias": {
"vue": "./node_modules/vue/dist/vue.common.js"
},
现在,重新启动构建并试用您的新Web应用程序!
npm run dev
本教程仅概述您可以使用Node.js和TypeScript进行的操作。 以下是更多探索资源。
您可以在GitHub上找到完整的Guitar Inventory项目。
关注我们以获取更多精彩内容和我们团队的最新消息! 您可以在Twitter , Facebook和LinkedIn上找到我们。 有什么问题吗 在下面的评论中打我们。
翻译自: https://www.sitepoint.com/how-to-use-typescript-to-build-a-node-api-with-express/