cnpm i express mongoose body-parser bcryptjs jsonwebtoken morgan cors validator helmet dotenv multer http-status-codes -S
cnpm i typescript @types/node @types/express @types/mongoose @types/bcryptjs @types/jsonwebtoken @types/morgan @types/cors @types/validator ts-node-dev @types/helmet @types/multer -D
npx tsconfig.json
生成tsconfig文件。tsc --init
来说,这种方法相当于直接用适配好的ts配置。 "scripts": {
"build": "tsc",
"start": "ts-node-dev --respawn src/index.ts"
},
nodemon --exec ts-node --files src/index.ts
来执行。JWT_SECRET_KEY = 密钥
MONGODB_URL=数据库地址
process.env.JWT_SECRET_KEY
拿到值。import express from 'express'
import mongoose from 'mongoose'
import cors from 'cors'
import morgan from 'morgan'
import helmet from 'helmet'
import 'dotenv/config'
import path from 'path';
const app =express()
app.use(cors())
app.use(morgan('dev'))
app.use(helmet())
app.use(express.static(path.join(__dirname,'public')))
app.use(express.json())
app.use(express.urlencoded({extended:true}))
app.get('/',(_req,res,_next)=>{
res.json({success:true,data:'xx'})
});
(async function(){
await mongoose.set('useNewUrlParser',true)
await mongoose.set('useUnifiedTopology',true)
const MONGODB_URL = process.env.MONGODB_URL||'mongodb://localhost/tsbackend'
await mongoose.connect(MONGODB_URL)
const PORT =process.env.PORT||3000
app.listen(PORT,()=>{
console.log(`running on http://localhost:${PORT}`);
})
})()
export interface Application extends EventEmitter, IRouter, Express.Application {
...
...
use: ApplicationRequestHandler<this>;
}
export type ApplicationRequestHandler<T> = IRouterHandler<T> & IRouterMatcher<T> & ((...handlers: RequestHandlerParams[]) => T);
export type RequestHandlerParams<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any>
= RequestHandler<P, ResBody, ReqBody>
| ErrorRequestHandler<P, ResBody, ReqBody>
| Array<RequestHandler<P>
| ErrorRequestHandler<P>>;
export type ErrorRequestHandler<P extends Params = ParamsDictionary, ResBody = any, ReqBody = any> = (err: any, req: Request<P, ResBody, ReqBody>, res: Response<ResBody>, next: NextFunction) => any;
class HTTPException {
constructor(public status:number,public message:string,public errors?:any){
this.status=status
this.message=message
this.errors = errors||{}
}
}
export default HTTPException
import {Request,Response,NextFunction}from 'express'
import {INTERNAL_SERVER_ERROR} from 'http-status-codes'
import HTTPException from '../exceptions/HTTPException'
const errorMiddleware = (err:HTTPException,_req:Request,res:Response,_next:NextFunction)=>{
res.status(err.status||INTERNAL_SERVER_ERROR).json({
success:false,
message:err.message,
errors:err.errors
})
}
export default errorMiddleware
app.get('/',(_req,res,_next)=>{
res.json({success:true,data:'xx'})
});
app.use((_req:Request,_res:Response,next:NextFunction)=>{
const error :HTTPException=new HTTPException(404,'未分配路由')
next(error)
})
app.use(errorMiddleware);
import mongoose ,{Schema,Model,Document} from 'mongoose'
import validator from 'validator'
export interface UserDocument extends Document {
username:string,
password:string,
avatar:string,
email:string
}
const UserSchema :Schema<UserDocument>= new Schema({
username:{
type:String,
required:[true,'用户名不为空'],
minlength:[6,'最小长度不能小于6位'],
maxlength:[12,'最大长度不得大于12位']
},
password:String,
avatar:String,
email:{
type:String,
validate:{
validator:validator.isEmail
},
trim:true
}
},{timestamps:true})
export const User:Model<UserDocument> = mongoose.model('User',UserSchema)
export function model<T extends Document>(name: string, schema?: Schema, collection?: string,
skipInit?: boolean): Model<T>;
import {register} from './controllers'
app.post('/user/register',register)
src/controllers/user.ts
import {NextFunction,Response,Request} from 'express'
import { User } from '../models'
export const register = async(req:Request,res:Response,_next:NextFunction)=>{
let {username,password,confirmpassword,email}=req.body;
let user = new User({username,password,confirmpassword,email})
await user.save()
res.json({
success:true,
data:user
})
}
src/controllers/index.ts
export * from './user'
{
"username": "1111111",
"password": "11",
"confirmpassword": "11",
"email": "111@qq.com"
}
成功后会返回success和data的对象。
下面完善用户提交信息校验,在src下建个utils目录,下面建个validator.ts
import validator from 'validator'
import {UserDocument} from '../models'
export interface RegisterInput extends Partial<UserDocument>{
confirmpassword?:string
}
export interface RegisterResult {
valid:boolean,
errors:RegisterInput
}
export const validatorRegisterInput = (username:string,password:string,
confirmpassword:string,email:string)=>{
let errors:RegisterInput={}
if(username===undefined||username.length==0){
errors.username='用户名不能为空'
}
if(password===undefined||password.length==0){
errors.password='密码不能为空'
}
if(confirmpassword===undefined||confirmpassword.length==0){
errors.confirmpassword='确认密码不能为空'
}
if(email===undefined||email.length==0){
errors.email='邮箱不能为空'
}
if(!validator.isEmail(email)){
errors.email='邮箱不正确'
}
return {valid:Object.keys(errors).length==0,errors}
}
import {NextFunction,Response,Request} from 'express'
import { User } from '../models'
import {validatorRegisterInput} from '../utils/validator'
import HTTPException from '../exceptions/HTTPException';
import { UNPROCESSABLE_ENTITY } from 'http-status-codes';
export const register = async(req:Request,res:Response,_next:NextFunction)=>{
let {username,password,confirmpassword,email}=req.body;
let {valid,errors}= validatorRegisterInput(username,password,confirmpassword,email)
try {
if(!valid){
throw new HTTPException(UNPROCESSABLE_ENTITY,'用户提交数据不正确',errors)
}
let users = await User.findOne({username})
if(users){
throw new HTTPException(UNPROCESSABLE_ENTITY,'用户名重复',errors)
}
let user = new User({username,password,confirmpassword,email})
await user.save()
res.json({
success:true,
data:user
})
} catch (error) {
_next(error)
}
}
UserSchema.pre<UserDocument>('save',async function(next){
if(!this.isModified('password')){
return next()
}
try {
this.password= await bcryptjs.hash(this.password,10)
next()
} catch (error) {
next(error)
}
})
import {register,login} from './controllers'
app.post('/user/login',login)
export const login = async(req:Request,res:Response,_next:NextFunction)=>{
let {username,password}=req.body;
try {
let user =await User.login(username,password)
if(!user){
throw new HTTPException(UNAUTHORIZED,'登录失败')
}else{
res.json({
success:true,
data:user
})
}
} catch (error) {
_next(error)
}
}
UserSchema.static('login',async function(this:any,username:string,password:string){
let user=await this.findOne({username})
if(user){
const match =await bcryptjs.compare(password,user.password)
if(match){
return user
}else{
return null
}
}else{
return null
}
})
interface UserModel extends Model<UserDocument>{
login:(username:string,password:string)=>UserDocument|null
}
export const User:UserModel = mongoose.model<UserDocument,UserModel>('User',UserSchema)
Model<UserDocument>
,这个类型是没有login的方法的。所以我们需要对其扩展。 export function model<T extends Document>(name: string, schema?: Schema, collection?: string,
skipInit?: boolean): Model<T>;
export function model<T extends Document, U extends Model<T>>(
name: string,
schema?: Schema,
collection?: string,
skipInit?: boolean
): U;
Model<T>
,如果T后来又进行修改,那类型就不是T了。所以使用第二种方式,U来继承model<T>
,而T继承了Document,最后返回了U,也就是U是修改后的T。export const login = async(req:Request,res:Response,_next:NextFunction)=>{
let {username,password}=req.body;
try {
let user =await User.login(username,password)
if(!user){
throw new HTTPException(UNAUTHORIZED,'登录失败')
}else{
let access_token = await user.getToken()
res.json({
success:true,
data:access_token
})
}
} catch (error) {
_next(error)
}
}
export interface UserDocument extends Document {
username:string,
password:string,
avatar:string,
email:string,
getToken:()=>string
}
UserSchema.methods.getToken = function(this:UserDocument){
let payload = {id:this._id}
return jwt.sign(payload,process.env.JWT_SECRET_KEY||'YEHUOZHILI',{expiresIn:'1h'})
}
app.get('/user/validate',validate)
Authorization:Bearer token
export const validate=async(req:Request,res:Response,_next:NextFunction)=>{
const authorization =req.headers.authorization
if(authorization){
const access_token = authorization.split(' ')[1]
if(access_token){
try {
const UserPayload:UserPayload=jwt.verify(access_token,process.env.JWT_SECRET_KEY||'YEHUOZHILI')as UserPayload
const user = await User.findById(UserPayload.id)
if(user){
res.json({
success:true,
data:user.toJSON()
})
}else{
_next(new HTTPException(UNAUTHORIZED,'无此用户'))
}
} catch (error) {
_next(new HTTPException(UNAUTHORIZED,'access_token无效'))
}
}else{
_next(new HTTPException(UNAUTHORIZED,'access_token无效'))
}
}else{
_next(new HTTPException(UNAUTHORIZED,'authorization未提供'))
}
}
export interface UserPayload{
id:string
}
const UserSchema :Schema<UserDocument>= new Schema({
username:{
type:String,
required:[true,'用户名不为空'],
minlength:[6,'最小长度不能小于6位'],
maxlength:[12,'最大长度不得大于12位']
},
password:String,
avatar:String,
email:{
type:String,
validate:{
validator:validator.isEmail
},
trim:true
}
},{timestamps:true,toJSON:{
transform:function(_doc:any,result:any){
result.id = result._id
delete result._id
delete result.__v
delete result.password
delete result.createdAt
delete result.updatedAt
return result
}
}})