erui _ eruie 002如何使用TypeScript通过Express构建节点API
Ťhis article was originally published on the Okta developer blog. Thank you for supporting the partners who make SitePoint possible.
今天,构建JavaScript应用程序大规模地仍然是一个挑战。 越来越多的团队开始使用TypeScript来补充其JavaScript项目。
TypeScript不是另一种语言。 灵活超集JavaScript以及描述可选数据类型的方法。 所有“标准”和有效的JavaScript也是有效的TypeScript。 您可以拨入任意数量的电话。
在吉他演奏家中,每个人都在开玩笑应该了解。
问:“你有几把吉他?需要?”一个: ”ñ + 1. Always oñe more.”
In this tutorial, you are going to create a new Node.js application to keep track of an inventory of guitars. In a nutshell, this tutorial uses Node.js with Express, EJS, and PostgreSQL on the backend, Vue, Materialize, and Axios on the frontend, Okta for account registration and authorization, and TypeScript to govern the JavaScripts!
node --versionIf you get an error, or the version of Node.js you have is less than version 8, you’ll need to install Node.js. On Mac or Linux, I recommend you first install nvm and use nvm to install Node.js. On Windows, I recommend you use Chocolatey.
mkdir guitar-inventory cd guitar-inventoryUse
npmto initialize a
package.jsonfile.
npm init -y
In this sample application, Express is used to serve web pages and implement an API. Dependencies are installed using
npm. Add Express to your project with the following command.
npm install express
If you don’t already have a favorite code editor, I use and recommend Visual Studio Code. VS Code has exceptional support for JavaScript and Node.js, such as smart code completion and debugging, and there’s a vast library of free extensions contributed by the community.
Create a folder named
src. In this folder, create a file named
index.js. Open the file and add the following 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 }` ); } );
Next, update
package.jsonto instruct
npmon how to run your application. Change the
mainproperty value to point to
src/index.js, and add a
startscript to the
scriptsobject.
"main": "src/index.js", "scripts": { "start": "node .", "test": "echo \"Error: no test specified\" && exit 1" },
npm run start
server started at http://localhost:8080Launch your browser and navigate to
http://localhost:8080. You should see the text “Hello world!”
Note: To stop the web application, you can go back to the terminal or command prompt and press
CTRL+C.
The first step is to add the TypeScript compiler. You can install the compiler as a developer dependency using the
--save-devflag.
npm install --save-dev typescript
The next step is to add a
tsconfig.jsonfile. This file instructs TypeScript how to compile (transpile) your TypeScript code into plain JavaScript.
Create a file named
tsconfig.jsonin the root folder of your project, and add the following configuration.
{ "compilerOptions": { "module": "commonjs", "esModuleInterop": true, "target": "es6", "noImplicitAny": true, "moduleResolution": "node", "sourceMap": true, "outDir": "dist", "baseUrl": ".", "paths": { "*": [ "node_modules/*" ] } }, "include": [ "src/**/*" ] }
Based on this
tsconfig.jsonfile, the TypeScript compiler will (attempt to) compile any files ending with
.tsit finds in the
srcfolder, and store the results in a folder named
dist. Node.js uses the CommonJS module system, so the value for the
modulesetting is
commonjs. Also, the target version of JavaScript is ES6 (ES2015), which is compatible with modern versions of Node.js.
It’s also a great idea to add
tslintand create a
tslint.jsonfile that instructs TypeScript how to lint your code. If you’re not familiar with linting, it is a code analysis tool to alert you to potential problems in your code beyond syntax issues.
Install
tslintas a developer dependency.
npm install --save-dev typescript tslint
Next, create a new file in the root folder named
tslint.jsonfile and add the following configuration.
{ "defaultSeverity": "error", "extends": [ "tslint:recommended" ], "jsRules": {}, "rules": { "trailing-comma": [ false ] }, "rulesDirectory": [] }
Next, update your
package.jsonto change
mainto point to the new
distfolder created by the TypeScript compiler. Also, add a couple of scripts to execute TSLint and the TypeScript compiler just before starting the Node.js server.
"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" },
Finally, change the extension of the
src/index.jsfile from
.jsto
.ts, the TypeScript extension, and run the start script.
npm run start
Note: You can run TSLint and the TypeScript compiler without starting the Node.js server using
npm run build.
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 ) => { ~~~The two most common errors you may see are syntax errors and missing type information. TSLint considers using
console.logto be an issue for production code. The best solution is to replace uses of console.log with a logging framework such as winston. For now, add the following comment to
src/index.tsto disable the rule.
app.listen( port, () => { // tslint:disable-next-line:no-console console.log( `server started at http://localhost:${ port }` ); } );
TypeScript prefers to use the
importmodule syntax over
require, so you’ll start by changing the first line in
src/index.tsfrom:
const express = require( "express" );
import express from "express";To assist TypeScript developers, library authors and community contributors publish companion libraries called ŤypeScript declaration files. Declaration files are published to the DefinitelyŤyped open source repository, or sometimes found in the original JavaScript library itself.
npm install --save-dev @types/node @types/express
npm run startYour Node.js application is off to a great start, but perhaps not the best looking, yet. This step adds Materialize, a modern CSS framework based on Google’s Material Design, and Embedded JavaScript Templates (EJS), an HTML template language for Express. Materialize and EJS are a good foundation for a much better UI.
npm install ejsNext, make a new folder under
/srcnamed
views. In the
/src/viewsfolder, create a file named
index.ejs. Add the following code to
/src/views/index.ejs.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Guitar Inventory</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> </head> <body> <div class="container"> <h1 class="header">Guitar Inventory</h1> <a class="btn" href="/guitars"><i class="material-icons right">arrow_forward</i>Get started!</a> </div> </body> </html>
Update
/src/index.tswith the following code.
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 }` ); } );
The TypeScript compiler does the work of generating the JavaScript files and copies them to the
distfolder. However, it does not copy the other types of files the project needs to run, such as the EJS view templates. To accomplish this, create a build script that copies all the other files to the
distfolder.
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
. Use to run TypeScript files directly.shelljs
. Use to execute shell commands such as to copy files and remove directories.fs-extra
. A module that extends the Node.js file system (fs
) module with features such as reading and writing JSON files.rimraf
. Use to recursively remove folders.npm-run-all
. Use to execute multiplenpm
scripts sequentially or in parallel.nodemon
. A handy tool for running Node.js in a development environment. Nodemon watches files for changes and automatically restarts the Node.js application when changes are detected. No more stopping and restarting Node.js!
Make a new folder in the root of the project named
tools. Create a file in the
toolsfolder named
copyAssets.ts. Copy the following code into this file.
import * as shell from "shelljs"; // Copy all the view templates shell.cp( "-R", "src/views", "dist/" );
Update the
scriptsin
package.jsonto the following code.
"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" },
Note: If you are not familiar with using
npmscripts, they can be very powerful and useful to any Node.js project. Scripts can be chained together in several ways. One way to chain scripts together is to use thepreandpostprefixes. For example, if you have one script labeledstartand another labeledprestart, executingnpm run startat the terminal will first runprestart, and only after it successfully finishes doesstartrun.
npm run dev
Node.js applications typically use environment variables for configuration. However, managing environment variables can be a chore. A popular module for managing application configuration data is dotenv.
Install
dotenvas a project dependency.
npm install dotenv npm install --save-dev @types/dotenv
Create a file named
.envin the root folder of the project, and add the following code.
# Set to production when deploying to production NODE_ENV=development # Node.js server configuration SERVER_PORT=8080
Note: When using a source control system such as
git, do not add the.envfile to source control. Each environment requires a custom.envfile. It is recommended you document the values expected in the.envfile in the project README or a separate.env.samplefile.
Now, update
src/index.tsto use
dotenvto configure the application server port value.
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 }` ); } );
You will use the
.envfor much more configuration information as the project grows.
Adding user registration and login (authentication) to any application is not a trivial task. The good news is Okta makes this step very easy. To begin, create a free developer account with Okta. First, navigate to developer.okta.com and click the Create Free Account button, or click the Sign Up button.
输入您的应用程序的名称,例如吉他库存。 验证端口号与为本地Web应用程序配置的端口号相同。 然后,单击“完成”以完成应用程序的创建。
Copy and paste the following code into your
.envfile.
# Okta configuration OKTA_ORG_URL=https://{yourOktaDomain} OKTA_CLIENT_ID={yourClientId} OKTA_CLIENT_SECRET={yourClientSecret}
In the Okta application console, click on your new application’s General tab, and find near the bottom of the page a section titled “Client Credentials.” Copy the Client ID and Client secret values and paste them into your
.envfile to replace
{yourClientId}and
{yourClientSecret}, respectively.
- 单击编辑按钮。将自助服务注册更改为已启用。Click the Save button at the bottom of the form。
The last step to securing your Node.js application is to configure Express to use the Okta OpenId Connect (OIDC) middleware.
npm install @okta/oidc-middleware express-session npm install --save-dev @types/express-session
Next, update your
.envfile to add a
HOST_URLand
SESSION_SECRETvalue. You may change the
SESSION_SECRETvalue to any string you wish.
# Node.js server configuration SERVER_PORT=8080 HOST_URL=http://localhost:8080 SESSION_SECRET=MySuperCoolAndAwesomeSecretForSigningSessionCookies
Create a folder under
srcnamed
middleware. Add a file to the
src/middlewarefolder named
sessionAuth.ts. Add the following code to
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; };
At this point, if you are using a code editor like VS Code, you may see TypeScript complaining about the
@okta/oidc-middlewaremodule. At the time of this writing, this module does not yet have an official TypeScript declaration file. For now, create a file in the
srcfolder named
global.d.tsand add the following code.
declare module "@okta/oidc-middleware";
As the application grows, you will add many more routes. It is a good idea to define all the routes in one area of the project. Make a new folder under
srcnamed
routes. Add a new file to
src/routesnamed
index.ts. Then, add the following code to this new file.
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" ); } ); };
Next, update
src/index.tsto use the
sessionAuthand
routesmodules you created.
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 }` ); } );
Next, create a new file for the guitar list view template at
src/views/guitars.ejsand enter the following HTML.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Guitar Inventory</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> </head> <body> <div class="container"> <h1 class="header">Guitar Inventory</h1> <p>Your future list of guitars!</p> </div> </body> </html>
npm run dev
注意:要验证身份验证是否按预期工作,请打开新的浏览器或使用专用/隐身浏览器窗口。
With authentication working, you can take advantage of the user profile information returned from Okta. The OIDC middleware automatically attaches a
userContextobject and an
isAuthenticated()function to every request. This
userContexthas a
userinfoproperty that contains information that looks like the following object.
{ sub: '00abc12defg3hij4k5l6', name: 'First Last', locale: 'en-US', preferred_username: 'account@company.com', given_name: 'First', family_name: 'Last', zoneinfo: 'America/Los_Angeles', updated_at: 1539283620 }
The first step is get the user profile object and pass it to the views as data. Update the
src/routes/index.tswith the following code.
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 } ); } ); };
Make a new folder under
src/viewsnamed
partials. Create a new file in this folder named
nav.ejs. Add the following code to
src/views/partials/nav.ejs.
<nav> <div class="nav-wrapper"> <a href="/" class="brand-logo"><% if ( user ) { %><%= user.name %>'s <% } %>Guitar Inventory</a> <ul id="nav-mobile" class="right hide-on-med-and-down"> <li><a href="/guitars">My Guitars</a></li> <% if ( isAuthenticated ) { %> <li><a href="/logout">Logout</a></li> <% } %> <% if ( !isAuthenticated ) { %> <li><a href="/login">Login</a></li> <% } %> </ul> </div> </nav>
Modify the
src/views/index.ejsand
src/views/guitars.ejsfiles. Immediately following the
<body>tag, insert the following code.
<body> <% include partials/nav %>
This tutorial uses PostgreSQL. To make things easier, use Docker to set up an instance of PostgreSQL. If you don’t already have Docker installed, you can follow the install guide.
docker pull postgres:latest
docker run -d --name guitar-db -p 5432:5432 -e 'POSTGRES_PASSWORD=p@ssw0rd42' postgres
Note: If you already have PostgreSQL installed locally, you will need to change the-pparameter to map port 5432 to a different port that does not conflict with your existing instance of PostgreSQL.
-d
– This launches the container in daemon mode, so it runs in the background.-name
– This gives your Docker container a friendly name, which is useful for stopping and starting containers.-p
– This maps the host (your computer) port 5432 to the container’s port 5432. PostgreSQL, by default, listens for connections on TCP port 5432.-e
– This sets an environment variable in the container. In this example, the administrator password isp@ssw0rd42
. You can change this value to any password you desire.postgres
– This final parameter tells Docker to use the postgres image.
Note: If you restart your computer, may need to restart the Docker container. You can do that using the
docker start guitar-dbcommand.
npm install pg pg-promise npm install --save-dev @types/pgAdd the following settings to the end of the
.envfile.
# Postgres configuration PGHOST=localhost PGUSER=postgres PGDATABASE=postgres PGPASSWORD=p@ssw0rd42 PGPORT=5432
Note: If you changed the database administrator password, be sure to replace the default
p@ssw0rd42with that password in this file.
You need a build script to initialize the PostgreSQL database. This script should read in a
.pgsqlfile and execute the SQL commands against the local database.
In the
toolsfolder, create two files:
initdb.tsand
initdb.pgsql. Copy and paste the following code into
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" ); } );
Next, copy and paste the following code into
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 );
Next, add a new script to
package.json.
"initdb": "ts-node tools/initdb",
npm run initdbYou should see the message
finishedat the console. A new table named
guitarsis now in your database! Any time you want to reset your database, just rerun the script.
To complete the API, you need to add new routes to Express to create, query, update, and delete guitars. First, create a new file under
src/routesnamed
api.ts. Add the following code to this file.
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 } ); } } ); };
Update
src/routes/index.tsto include the new
apimodule.
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 ); };
Finally, update
src/index.tsto add a new configuration option immediately following the line to create the Express application. This code enables Express to parse incoming JSON data.
const app = express(); // Configure Express to parse incoming JSON data app.use( express.json() );
This final step of the project uses Vue for frontend rendering, Axios for making HTTP calls to the backend API, and Parcel to both transpile TypeScript and bundle all the dependencies together into a single JavaScript file.
npm install axios vue materialize-css npm install --save-dev parcel-bundler @types/axios @types/materialize-css @types/vueMake a new folder under
srcnamed
public. Make a new folder under
src/publicnamed
js. Create a file under
src/public/jsnamed
main.tsand add the following code.
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(); } } );
Update
tsconfig.jsonto exclude the
src/publicfolder from the backend Node.js build process.
{ "compilerOptions": { "module": "commonjs", "esModuleInterop": true, "target": "es6", "noImplicitAny": true, "moduleResolution": "node", "sourceMap": true, "outDir": "dist", "baseUrl": ".", "paths": { "*": [ "node_modules/*" ] } }, "include": [ "src/**/*" ], "exclude": [ "src/public" ] }
Create a new
tsconfig.jsonfile under
src/public/jsand add the following code. This TypeScript configuration is to compile
main.tsfor use in the browser.
{ "compilerOptions": { "lib": [ "es6", "dom" ], "noImplicitAny": true, "allowJs": true, "target": "es5", "strict": true, "module": "es6", "moduleResolution": "node", "outDir": "../../../dist/public/js", "sourceMap": true } }
Next, update
src/index.tsto configure Express to serve static files from the
publicfolder. Add this line after the code that configures Express to use
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" ) ) );
Update
src/views/guitars.ejsto add the Vue application template and a reference to the
js/main.jsfile.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Guitar Inventory</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> </head> <body> <% include partials/nav %><div class="container"> <div id="app"> <div class="row" id="guitarList"> <h3>Guitar list</h3> <table v-if="hazGuitars"> <thead> <tr> <th>Year</th> <th>Brand</th> <th>Model</th> <th>Color</th> <th></th> </tr> </thead> <tbody> <tr v-for="guitar in guitars"> <td></td> <td></td> <td></td> <td></td> <td> <button id="guitarDelete" @click="confirmDeleteGuitar(guitar.id)" class="btn-small"><i class="material-icons right">delete</i>Delete</button> </td> </tr> </tbody> </table> <p v-if="noGuitars">No guitars yet!</p> </div> <div class="row" id="guitarEdit"> <h3>Add a guitar</h3> <form class="col s12" @submit.prevent="addGuitar"> <div class="row"> <div class="input-field col s6"> <input v-model="year" ref="year" placeholder="2005" id="year" type="text" class="validate"> <label for="brand">Year</label> </div> <div class="input-field col s6"> <input v-model="brand" ref="brand" placeholder="Paul Reed Smith" id="brand" type="text" class="validate"> <label for="brand">Brand</label> </div> </div> <div class="row"> <div class="input-field col s6"> <input v-model="model" ref="model" placeholder="Custom 24" id="model" type="text" class="validate"> <label for="model">Model</label> </div> <div class="input-field col s6"> <input v-model="color" ref="color" placeholder="Whale Blue" id="color" type="text" class="validate"> <label for="model">Color</label> </div> </div> <button id="guitarEditSubmit" class="btn" type="submit"><i class="material-icons right">send</i>Submit</button> </form> </div> <div id="deleteConfirm" ref="deleteConfirm" class="modal"> <div class="modal-content"> <h4>Confirm delete</h4> <p>Delete ?</p> </div> <div class="modal-footer"> <button @click="deleteGuitar(selectedGuitarId)" class="modal-close btn-flat">Ok</button> <button class="modal-close btn-flat">Cancel</button> </div> </div> </div> </div> <script src="js/main.js"></script></body> </html>
Finally, update
package.jsonto add a new
parcelscript, update the
buildscript, and add a new
aliassection for Vue. The
aliassection points Parcel to the correct Vue file to bundle with
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" },
npm run dev
- TypeScript Learning Resources by Jonathan Creamer
- TypeScript Node Starter – an open-source project by Microsoft
- TypeScript Deep Dive – Free online book by Basarat Ali Syed
- TypeScript Documentation
- Vue TypeScript Support
- Simple Node Authentication
You can find the completed Guitar Inventory project on GitHub.
Follow us for more great content and updates from our team! You can find us on Twitter, Facebook, and LinkedIn. Questions? Hit us up in the comments below.
from: https://www.sitepoint.com//how-to-use-typescript-to-build-a-node-api-with-express/
dangzhuang7815 原创文章 0获赞 0访问量 303 关注 私信- erui _ eruie 002使用Express,Angular和GraphQL构建一个简单的Web应用程序
- 使用Node.js和MongoDB通过Mongoshin和Express.js构建JSON REST API服务器
- erui _ eruie 002使用Express在Node中构建您的第一个路由器
- 第8章-使用Express.js和Hapi构建Node.js-REST-API服务-8.5.小结
- 如何快速通过json构建javabean对象(Intellij IDEA-->GsonFormat使用教程)
- [android插件篇]如何快速通过json构建javabean对象(GsonFormat使用教程)
- 第8章-使用Express.js和Hapi构建Node.js-REST-API服务-8.4.重构:使用Hapi搭建REST API服务器
- SpringBoot中如何使用Swagger2快速构建API文档?
- erui _ eruie 002使用Angular 7中的所有新功能和值得关注的功能构建应用程序
- 如何将传递节点设置为通过使用 Visual C#.NET 内联 XSLT 脚本函数
- 如何快速通过json构建javabean对象(Intellij IDEA-->GsonFormat使用教程)
- 通过 ActiveMQ 演示如何使用 JMS API (入门)
- 通过使用的新的浏览器API过滤多余节点
- c++模板类构建AVlL树及AVL树的单双旋转图文简述,以及插入新节点后如何通过旋转使之继续保持平衡
- 第8章-使用Express.js和Hapi构建Node.js-REST-API服务-8.2.项目依赖
- 第8章-使用Express.js和Hapi构建Node.js-REST-API服务-8.3.使用Express和Mongoskin实现REST API服务器
- Node.js项目实战-构建可扩展的Web应用(第一版):8 使用Express.js和Hapi构建Node.js REST API服务
- 使用Express和GraphQL构建简单的API服务
- 第8章-使用Express.js和Hapi构建Node.js-REST-API服务-8.1.REST(表述性状态传递)Representational State Transfer
- Dom元素基本操作方法API,先记录下,方便以后使用。 W3C DOM和JavaScript很容易混淆不清。DOM是面向HTML和XML文档的API,为文档提供了结构化表示,并定义了如何通过脚本