一次TypeScript, React, Node, MongoDB的模板式先後端分離開發實踐

前言

在大概1年前接觸了typescript以後, 日漸被它所吸引. 甚至一個簡單的本地測試文件node ./test.js有時也會切到ts-node ./test.ts. 在一樣的時間節點以前, 仍是會不時地去學學node, mongodb相關的. 但是, 因爲懶(需)惰(求), 在好久沒碰以後, 不少知識點都忘了!😴node

綜上, 因而就有了今天這個話題:react

如何在工做時間之餘完成本身的我的項目並實現按時上牀睡覺webpack

答案是: 不存在的😅ios

項目簡介

項目會不斷維護. 不管是client端仍是server端, 都只提供簡單的模板式的功能.git

地址

client ts-react-webpackgithub

server showcaseweb

線上體驗mongodb

依賴

typescript是兩端的基調typescript

client

  • webpack-4.x
  • typescript-3.0.x
  • react-16.4.x
  • mobx-5.x
  • ant design
  • ...

詳看express

server

centos上mongodb的官網安裝教程, 其餘系統請自行查閱.

  • nestjs
  • dotenv
  • jsonwebtoken
  • mongodb(mongoose)
  • ...

須要講一下我爲何選了nestjs:

nestjstypeScript引入並基於express封裝. 意味着, 它與絕大部分express插件的兼容性都很好.

nestjs的核心概念是提供一種體系結構, 它幫助開發人員實現層的最大分離, 並在應用程序中增長抽象.

此外, 它對測試是很是友好的...

也須要聲明的是, nestjs的依賴注入特性是受到了angular框架的啓發, 相信作angular開發的對整個程序體系會更容易看懂.

查看中文文檔

具體實現

server

簡單介紹下幾個主流程模塊

main.ts

我是用nest-cli工具初始化項目的, 一切從src/main.ts開始

import { NestFactory } from '@nestjs/core'
import * as dotenv from 'dotenv'
import { DOTENV_PATH } from 'config'

// 優先執行, 避免引用項目模塊時獲取環境變量失敗
dotenv.config({ path: DOTENV_PATH })

import { AppModule } from './app.module'

async function bootstrap() {
    const app = await NestFactory.create(AppModule)
    // 支持跨域
    app.enableCors()
    await app.listen(9999)
}
bootstrap()
複製代碼

一樣地, 咱們能夠提供一個express實例到NestFactory.create:

const server = express();
const app = await NestFactory.create(ApplicationModule, server);
複製代碼

這樣咱們就能夠徹底控制express實例生命週期, 好比官方FAQ中說到的建立幾個同時運行的服務器

在我本地開發的時候, 根目錄上還有一個.dev.env, 這是未提交到github的, 由於裏面包含了我我的的mongodb遠程ip地址 其餘內容與github上的.env一致, 由於我本地並不想再安裝一遍mongodb, 若是是想把項目拉下來就跑起來的, 不管如何你都須要一個mongodb服務, 固然你是能夠本地安裝就行了.

還須要說起到一點就是調試:

之前在vscode上調試node程序都須要在調試欄新增配置, 而後利用該配置去跑起應用才能實現斷點調試, 新版的vscode支持autoAttach功能, 使用Command + Shift + P 喚起設置功能面板

啓動它!

這樣, 在項目的.vscode/setting.json裏面會多了一個選項: "debug.node.autoAttach": "on", 在咱們的啓動script裏面加上--inspect-brk就能夠實現vscode的斷點調試了. 對應地, npm run start:debug是個人啓動項, 可參考nodemon.debug.json

app.module.ts

import { Module } from '@nestjs/common'
import { MongooseModule } from '@nestjs/mongoose'

import { DB_CONN } from 'config/db'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import modules from 'routers'

@Module({
    imports: [
        MongooseModule.forRoot(DB_CONN, {
            useNewUrlParser: true,
        }),
        ...modules,
    ],
    controllers: [AppController],
    providers: [AppService],
})
export class AppModule {}
複製代碼

每一個 Nest 應用程序至少有一個模塊, 即根模塊. 根模塊是 Nest 開始安排應用程序樹的地方. 事實上, 根模塊多是應用程序中惟一的模塊, 特別是當應用程序很小時, 可是對於大型程序來講這是沒有意義的. 在大多數狀況下, 您將擁有多個模塊, 每一個模塊都有一組緊密相關的功能. 固然, 模塊間也能夠共享.

概念 解釋
providers Nest注入器實例化的提供者,而且能夠至少在整個模塊中共享
controllers 必須建立的一組控制器
imports 導入模塊所需的導入模塊列表
exports 此模塊提供的提供者的子集, 並應在其餘模塊中使用

參考module的文檔

AppController在這個程序當中只是爲了測試能返回Hello World!!!, 其實它不是必須的, 咱們能夠把它直接幹掉, 把所有接口, 所有邏輯放到各個module中實現, 以modules/user爲例, 接着往下看.

modules/user

目錄結構

user
├── dto -------------- 數據傳輸對象
├── index.ts --------- UserModule, 概念同AppModule
├── controller.ts ---- 傳統意義的控制器, `Nest`會將控制器映射到相應的路由
├── interface.ts ----- 類型聲明
├── schema.ts -------- mongoose schema
├── service.ts ------- 處理邏輯
複製代碼

有必要講講controller.tsservice.ts, 這是nestjs的概念中很重要的部分

controller.ts

import { Get, Post, Body, Controller } from '@nestjs/common'

import UserService from './service'
import CreateDto from './dto/create.dto'

@Controller('user')
export default class UserController {
    constructor(private readonly userService: UserService) {}

    @Get()
    findAll() {
        return this.userService.findAll()
    }

    @Post('create')
    create(@Body() req: CreateDto) {
        return this.userService.create(req)
    }
}
複製代碼

裝飾器路由爲每一個路由聲明瞭前綴,因此Nest會在這裏映射每一個/user的請求

@Get()裝飾器告訴Nest建立此路由路徑的端點

一樣地, @Post()也是如此, 而且這類Method裝飾器接收一個path參數, 如@Post('create'), 那麼咱們就能夠實現post到路徑/user/create

到此, 日後的邏輯交給service實現

service.ts

import { Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { Model } from 'mongoose'

import logger from 'utils/logger'
import { cryptData } from 'utils/common'
import ServiceExt from 'utils/serviceExt'
import { IUser } from './interface'
import CreateDto from './dto/create.dto'

@Injectable()
export default class UserService extends ServiceExt {
    constructor(@InjectModel('User') private readonly userModel: Model<IUser>) {
        super()
    }

    async create(createDto: CreateDto) {
        if (!createDto || !createDto.account || !createDto.password) {
            logger.error(createDto)
            return this.createResData(null, '參數錯誤!', 1)
        }
        const isUserExist = await this.isDocumentExist(this.userModel, {
            account: createDto.account,
        })
        if (isUserExist) {
            return this.createResData(null, '用戶已存在!', 1)
        }
        const createdUser = new this.userModel({
            ...createDto,
            password: cryptData(createDto.password),
        })
        const user = await createdUser.save()
        return this.createResData(user)
    }

    async findUserByAccount(account: string) {
        const user = await this.userModel.findOne({ account })
        return user
    }

    async findAll() {
        const users = await this.userModel.find({})
        return this.createResData(users)
    }
}

複製代碼

至此, 咱們運行npm run start:dev啓動一下服務:

直接在瀏覽器端訪問http://localhost:9999/#/

沒錯, 的確失敗了!!! 由於咱們使用了jsonwebtoken, 在modules/auth能夠看到它的實現.

如今咱們在postman中登陸了再試試吧!

bingo!!!

(若是是想拉下來跑的話, 也能夠照着schema的格式用postman先僞造條用戶數據, 把系統打通!!!)

client

關於client端的實現我不會細講, 能夠看項目github, 和我以前的文章(typescript-react-webpack4 起手與踩坑), 項目結構會有改動.

講一下接入了真實服務器以後http請求對於token的一些處理, 查看http.ts

首先是建立axios實例時須要在header處把token帶上

const axiosConfig: AxiosRequestConfig = {
    method: v,
    url,
    baseURL: baseUrl || DEFAULTCONFIG.baseURL,
    headers: { Authorization: `Bearer ${getCookie(COOKIE_KEYS.TOKEN)}` }
}
const instance = axios.create(DEFAULTCONFIG)
複製代碼

token也能夠存放在localStorage

另一點是, 對應服務端返回的token錯誤處理

const TOKENERROR = [401, 402, 403]
let authTimer: number = null
...

if (TOKENERROR.includes(error.response.status)) {
    message.destroy()
    message.error('用戶認證失敗! 請登陸重試...')
    window.clearTimeout(authTimer)
    authTimer = window.setTimeout(() => {
        location.replace('/#/login')
    }, 300)
    return
}
複製代碼

總結

兩端項目都是簡單的模板項目, 不存在什麼繁雜的業務, 屬於比較初級的學習實踐. 對nestjs的掌握程度有限, 只是拿來練練手. 可能後續會基於這篇文章繼續深刻地去講講, 好比部署之類的, 兩個項目也會不斷去維護. 後續也有計劃會合二爲一. 看時間吧!

相關文章
相關標籤/搜索