The previous chapter introduced the construction of front-end projects, so in this chapter, we will build back-end projects based on Express
Technology stack
express
typescript
mongoose
Initialize the project
Project initialization
# initialization
yarn init -y
Initialize git repository
git init
# express installation
yarn add express
# installation typescript
# ts-node-dev listen for file changes restart the service and share the result of ts
Install express and Node declaration files
yarn add typescript ts-node-dev @types/express @types/node --dev
Copy the code
Package. json Added startup script
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/app.ts"
},
Copy the code
Root directory new. Gitignore
node_modules
build
Copy the code
Ts environment configuration, which was covered in the previous chapter
New tsconfig. Json
{
"compilerOptions": {
"outDir": "build"."target": "es5"."module": "commonjs"."sourceMap": true."esModuleInterop": true."forceConsistentCasingInFileNames": true."strict": true."skipLibCheck": true}}Copy the code
Entry file app.ts
Create a SRC directory and create app.ts in the directory
// src/app.ts
import express from 'express'
import routes from './routes' / / routing
const app = express()
app.use(express.json())
const PORT = 1337
/ / start
app.listen(PORT, async() = > {console.log(`App is running at http://localhost:${PORT}`)
routes(app)
})
Copy the code
routing
Create the routes directory and create the index.ts directory
// src/routes/index.ts
import { Express, Request, Response, Router } from 'express'
// Route configuration interface
interface RouterConf {
path: string.router: Router, meta? : unknow }// Route configuration
const routerConf: Array<RouterConf> = []
function routes(app: Express) {
/ / root directory
app.get('/'.(req: Request, res: Response) = > res.status(200).send('Hello Shinp!!! '))
routerConf.forEach((conf) = > app.use(conf.path, conf.router))
}
export default routes
Copy the code
Start the project
yarn dev
Localhost :1337
# You can see the browser output Hello Shinp!!
Copy the code
Code specifications (described in the previous chapter, essentially the same)
The reference configuration
// .eslintrc.js
module.exports = {
root: true.// Extend the rule
extends: [
'eslint:recommended'.'plugin:@typescript-eslint/recommended'.'plugin:prettier/recommended'.'prettier',].parserOptions: {
ecmaVersion: 12.parser: '@typescript-eslint/parser'.sourceType: 'module',},// Register the plug-in
plugins: ['@typescript-eslint'.'prettier'].// Rules are added as needed
rules: {
'no-var': 'error'.'no-undef': 0.'@typescript-eslint/consistent-type-definitions': ['error'.'interface',}}Copy the code
// .prettierrc.js
module.exports = {
tabWidth: 2.semi: false.singleQuote: true,}Copy the code
Project Common Configuration
Log output
Pino efficient Node.js logger, beautifying the output.
Dayjs is a minimalist date-manipulation JS library.
# installation
yarn add pino pino-pretty dayjs
Install declaration file
yarn add @types/pino --dev
Copy the code
Create logger.ts in the utils directory
// logger.ts
import pino from 'pino'
import dayjs from 'dayjs'
const log = pino({
transport: { // Pino 7.x is written differently
target: 'pino-pretty',},base: {
pid: false,},timestamp: () = > `,"time":"${dayjs().format()}"`,})export default log
Copy the code
Used in app. Ts
// app.ts is truncated
import logger from './utils/logger'
app.listen(POST, async () => {
logger.info(`App is running at http://localhost:${POST}`)
routes(app)
})
Copy the code
Global configuration
Config A lightweight and simple NodeJS configuration and deployment library
It is very simple to use, create config in the root directory, and use the API operations anywhere
The config directory is created in the root directory and default.json is created in the root directory
/* config/default.json */
{
"port": 1337
}
Copy the code
It can also be a TS file
// default.ts
export default {
port: 1337,}Copy the code
Used in app. Ts
// app.ts is truncated
import config from 'config'
const PORT = config.get<number> ('port')
app.listen(PORT, async () => {
logger.info(`App is running at http://localhost:${PORT}`)})Copy the code
Unified response parameter
Constants root directory for constants, code. Ts
// constants/code.ts
// Enumeration status code is defined according to your own needs
enum Code {
success = 3000,
denied,
error
}
enum CodeMessage {
success = 'Request successful',
denied = 'No permissions',
error = 'System exception'
}
// The state type can only be the state enumerated in Code
type codeType = keyof typeof Code
export { Code, codeType, CodeMessage }
Copy the code
Create commonres. ts under utils
// utils/commonRes.ts
// Edit as needed
import { Response } from 'express'
import { Code, codeType, CodeMessage } from '.. /constants/code'
interface ResOption {
type? : codeType status? :numbermessage? : unknow }// The response succeeds by default
function commonRes(res: Response, data: unknown, options? : ResOption) {
options = Object.assign({ type: Code[3000] }, options || {}) / / success by default
const { type, status, message } = options
let resStatus = status
if (resStatus === undefined) {
// Set the status code according to the status
resStatus = type === Code[3000]?200 : 409
}
// Response parameters
const sendRes: { code: number; data: unknown; message? : unknow } = {code: Code[type as codeType],
data,
}
// Response description
message && (sendRes.message = message)
return res.status(resStatus).send(sendRes)
}
// Error response
commonRes.error = function (res: Response, data: unknown, message? : unknown, status? :number) {
logger.error(message || CodeMessage['error'])
this(res, data, { type: 'error'.message: message || CodeMessage['error'].status: status || 409})}// No permission response
commonRes.denied = function (res: Response, data: unknown) {
this(res, data, { type: 'denied'.message: CodeMessage['denied'].status: 401})}export default commonRes
Copy the code
Routes used in the
// routes/index.ts
app.get('/'.(req: Request, res: Response) = > {
// commonRes(res, { word: 'Hello Shinp!!! '}, {type: 'success', message: 'request successful'}) succeeded
// commonres. denied(res, null) No permission
// commonres. error(res, null
commonRes(res, { word: 'Hello Shinp!!! ' }) / / success
})
Copy the code
Browser print
{
"code": 3000."data": {
"word": "Hello Shinp!!!"}}Copy the code
Static error capture
// utils/silentHandle.ts
// If there is an error during execution, the first element of the returned array is captured and assigned
async function silentHandle<T.U = Error> (fn: Function. args:Array<unknown>) :Promise"[U.null"|"null.T] >{
let result: [U, null"|"null, T]
try {
result = [null.awaitfn(... args)] }catch (e: any) {
result = [e, null]}return result
}
export default silentHandle
Copy the code
use
// routes/index.ts
import silentHandle from '.. /utils/silentHandle'
const getInfo = function () {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
Math.random() > . 5 ? resolve('info... ') : reject('error... ')},500)
})
}
app.get('/'.async (req: Request, res: Response) => {
const [e, result] = await silentHandle(getInfo)
e ? commonRes.error(res, null) : commonRes(res, { result })
})
Copy the code
Middleware – Response header information
Middleware is created in the root directory to hold middleware and responseHeader.ts is created in the root directory
// middleware/responseHeader.ts
import { Request, Response, NextFunction } from 'express'
const responseHeader = (req: Request, res: Response, next: NextFunction) = > {
const { origin, Origin, referer, Referer } = req.headers
// Wildcard if not manually set
const allowOrigin = origin || Origin || referer || Referer || The '*'
// Allow the request source
res.header('Access-Control-Allow-Origin', allowOrigin)
// Allow header fields
res.header('Access-Control-Allow-Headers'.'Content-Type')
// Allow public header fields
res.header('Access-Control-Expose-Headers'.'Content-Disposition')
// The allowed request mode
res.header('Access-Control-Allow-Methods'.'PUT,POST,GET,DELETE,OPTIONS')
// Cookies are allowed
res.header('Access-Control-Allow-Credentials'.'true')
// Precheck returns 204
if (req.method == 'OPTIONS') {
res.sendStatus(204)}else {
next()
}
}
export default responseHeader
Copy the code
Create index. Ts
import { Express } from 'express'
import express from 'express'
import responseHeader from './responseHeader'
function initMiddleware(app: Express) {
app.use(express.json())
app.use(responseHeader)
}
export default initMiddleware
Copy the code
App. In the ts
import initMiddleware from './middleware'
const app = express()
// Mount middleware
initMiddleware(app)
Copy the code
mongodb
The installation
Install mongodb on your system and use compass
Connect the mongo
# Mongoose operates mongodb's library
# zod type definition library
yarn add mongoose zod
yarn @types/mongoose --dev
Copy the code
configuration
// config/default.ts
// Add mongodb connection configuration
dbUri: 'mongodb://localhost:27017/shinp'.dbUser: 'root'.dbPassword: '* * * * * *'.dbAuthSource: 'admin'.Copy the code
// utils/dbConnect.ts
/ / db connection
import mongoose from 'mongoose'
import config from 'config'
import logger from './logger'
async function dbConnect() {
const dbUri = config.get<string> ('dbUri')
const dbUser = config.get<string> ('dbUser')
const dbPassword = config.get<string> ('dbPassword')
const dbAuthSource = config.get<string> ('dbAuthSource')
try {
const connection = await mongoose.connect(dbUri, {
user: dbUser,
pass: dbPassword,
authSource: dbAuthSource,
})
logger.info('DB connected')
return connection
} catch (error) {
logger.error('Could not connect to db')
process.exit(1)}}export default dbConnect
Copy the code
Used in app. Ts
// app.ts
import dbConnect from './utils/dbConnect'
app.listen(PORT, async () => {
logger.info(`App is running at http://localhost:${PORT}`)
await dbConnect()
routes(app)
})
Copy the code
If DB Connected is displayed on the console, the connection is successful
Defining data modules
Take user information as an example
Create models/user.model.ts in the root directory
// user.model.ts
import mongoose from 'mongoose'
import config from 'config'
// Template interface
export interface UserDocument extends mongoose.Document {
name: string
account: string
password: string
createdAt: Date
updatedAt: Date
deletedAt: Date
}
// Template verification rule
const userSchema = new mongoose.Schema(
{
name: { type: String.required: true },
account: { type: String.required: true },
password: { type: String.required: true}, {},timestamps: true,})/ / the only
userSchema.index({ account: 1.deletedAt: 1 }, { unique: true })
The template is automatically created in mongodb after execution
const UserModel = mongoose.model<UserDocument>('User', userSchema)
export default UserModel
Copy the code
Parameter calibration
Create schema/user.schema.ts in the root directory
// user.schema.ts
// Interface parameter verification mainly uses ZOD, specific usage can be viewed in the document
import { object.string, TypeOf } from 'zod'
// Create an interface
export const createUserSchema = object({
body: object({
account: string({ required_error: 'Missing account name' }).nonempty(),
name: string({ required_error: 'Missing user name' }).nonempty(),
password: string({ required_error: 'Missing user password' }).min(6.'Password too short - at least 6 characters'),
passwordConfirmation: string({ required_error: 'Confirmation password missing' }),
}).refine((data) = > data.password === data.passwordConfirmation, {
message: 'Passwords do not match twice'.path: ['passwordConfirmation',})})export type CreateUserInput = Omit<TypeOf<typeof createUserSchema>, 'body.passwordConfirmation'>
Copy the code
Added a middleware for verifying interface parameters
// middleware/validate.ts
import { Request, Response, NextFunction } from 'express'
import { AnyZodObject } from 'zod'
import { commonRes } from '.. /utils'
const validate = (schema: AnyZodObject) = > (req: Request, res: Response, next: NextFunction) = > {
try {
schema.parse({
body: req.body,
query: req.query,
params: req.params,
})
next()
} catch (e: any) {
return commonRes.error(res, null, e.errors)
}
}
export default validate
Copy the code
Generic database manipulation functions
Provides a set of common mongodb add, delete, modify and check operation methods
// utils/crudProvider.ts
// Add as needed
import { FilterQuery, UpdateQuery, DocumentDefinition, QueryOptions, Model, InsertManyOptions } from 'mongoose'
class BaseCrudProviderCls<document.Cdocument> {
private DBModel: Model<any>
constructor(DBModel: Model<any>) {
this.DBModel = DBModel
}
async create(input: DocumentDefinition<Cdocument>) {
const data = await this.DBModel.create(input)
return data.toJSON()
}
async update(query: FilterQuery<document>, update: UpdateQuery<document>, options? : QueryOptions) {
return this.DBModel.updateOne(query, update, options)
}
async find(query: FilterQuery<document>, projection? :any, options? : QueryOptions) {
const result = await this.DBModel.find(query, projection, options)
return result && result.map((d) = > d.toJSON())
}
}
const BaseCrudProvider = function <document.Cdocument> (DBModel: Model<any>) {
const CRUD = new BaseCrudProviderCls<document, Cdocument>(DBModel)
return {
create: CRUD.create.bind(CRUD),
update: CRUD.update.bind(CRUD),
find: CRUD.find.bind(CRUD),
}
}
export { BaseCrudProvider }
Copy the code
Database operations
Create service/user.service.ts in the root directory
// user.service.ts
/ / directing operations
import { BaseCrudProvider } from '.. /utils/crudProvider'
import UserModel, { UserDocument } from '.. /models/user.model'
const CRUD = BaseCrudProvider<UserDocument, Omit<UserDocument, 'createdAt'>>(UserModel)
export default CRUD
Copy the code
Interface processing
The root directory to create the controller/user. Controller. Ts
// user.controller.ts
import { Request, Response } from 'express'
import { commonRes, silentHandle } from '.. /utils'
import { CreateUserInput } from '.. /schema/user.schema'
import USER_CRUD from '.. /service/user.service'
export async function createUserHandler(req: Request<{}, {}, CreateUserInput['body']>, res: Response) {
const [e, user] = await silentHandle(USER_CRUD.create, req.body)
return e ? commonRes.error(res, null, e.message) : commonRes(res, user)
}
Copy the code
Route definition
Finally, add routes/user.routes.ts
import { Router } from 'express'
import { createUserHandler } from '.. /controller/user.controller'
import validate from '.. /middleware/validate'
import { createUserSchema } from '.. /schema/user.schema'
const router = Router()
// If interface parameters need to be verified, add verification middleware
router.post('/create', validate(createUserSchema), createUserHandler)
export default router
Copy the code
Introduced in routes/index.ts
import User from './user.routes'
const routerConf: Array<RouterConf> = [{ path: '/user'.router: User }]
Copy the code
Start the project debugging interface
Start the project, refresh the database in Compass, and you can see the Users module
Postman calls interface, add a record, can pass in different parameters test parameter verification
Now the mongodb configuration is complete, add modules as required
complete
At this point, the basic framework of the background project is completed, and the subsequent function can be expanded according to their own needs
The above is combined with their own development experience, as well as read other great god’s excellent code construction, mistakes, but also please correct, thank you!!
Some continuous integration related content may be recorded later for those who are interested.