如何搭建 Nuxt3+Nest 全栈式项目

前言

公司的屎山看多了,来学学 earthworm 中优雅代码。

earthworm 是一款通过连词成句这种更新颖方式学习英语的一款软件,其源码仓库在 github 上 starred 已经有 3.1k (日期截至 2024/04/22)。仓库地址在这儿,点击查看

本文来解析一下这个热门项目的项目结构,并仿造实现了一个示例项目,仓库地址在这儿,点击查看

earthworm 使用 pnpm 以 Monorepo 方式建立项目,技术栈上前端用到了 Nuxt3,后端用的是 Nest. js,数据库方面用到了 PostgreSQL 和 Redis,ORM 框架是 Drizzle ORM。项目工程化上使用 simple-git-hooks 作为 git hooks 方案,lint-staged 和 prettier 格式化代码。

初始化

新建项目,进入项目文件路径下 pnpm init 初始化项目。

monorepo 架构

pnpm 原生支持 monorepo,参考官网上给出的示例,点击查看

项目根目录下新建文件 pnpm-workspace.yaml, 文件内容如下:

yaml 复制代码
packages:
  - "apps/*"

同级目录下新建文件夹 apps,里面将存放前后端项目。

Nuxt3

进入 apps,创建前端项目。

shell 复制代码
pnpm dlx nuxi@latest init client

依据官网给出的脚手架创建项目,大概率会因为网络问题无法创建,你会得到如下这样的报错提示。

Error: Failed to download template from registry: Failed to download raw.githubusercontent.com/nuxt/starte...: TypeError: fetch failed

初始化

根据上面的错误日志,提示我们是因为下载项目模板失败,这个模板也是在 github 上,我们可以直接访问这个模板项目,点击查看

模板项目中,用法是执行 npx nuxi@latest init client 新建项目。但是这一步的创建也有可能会出现报错中断初始化,但是没关系我们还有最后一招,也就是手动创建。

手动创建的同时,我也会依次介绍每个文件目录的作用。

新建文件夹 client,初始化 pnpm init

安装依赖

因为使用了 monorepo,安装方式上和传统项目有点不同。

  • 安装 Nuxt3,typescript
shell 复制代码
pnpm add -D -F client nuxt typescript tsx
  • 安装 vue 相关
shell 复制代码
pnpm add -D -F client vue vue-router
pnpm add -F client pinia axios
  • 安装 css 框架,earthworm 中使用了目前较为流行的原子框架 tailwindcss
shell 复制代码
pnpm add -D -F client tailwindcss @nuxtjs/tailwindcss daisyui

最终你会得到如下这样的 package.json

json 复制代码
{
  "name": "client",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "postinstall": "nuxt prepare"
  },
  "dependencies": {
    "axios": "^1.6.7",
    "pinia": "^2.1.7"
  },
  "devDependencies": {
    "@nuxtjs/tailwindcss": "^6.11.4",
    "daisyui": "^4.7.3",
    "nuxt": "^3.10.3",
    "tailwindcss": "^3.4.1",
    "tsx": "^4.7.1",
    "vue": "^3.4.21",
    "vue-router": "^4.3.0"
  }
}

目录结构

目录结构大致如下:

  • api:接口请求文件
  • assets:静态文件,存放全局的 css,图片和音视频资源
  • components:组件,page 中可以抽离的部分都可以封装为组件放到这儿
  • composables:存放 hooks 文件,component 中 js 的相关逻辑封装成 hooks
  • layouts:布局,默认是 default.vue
  • pages: 页面,index.vue 为首页,其他的文件则是其他的路由地址
  • plugins:插件,例如我们安装的 pinia
  • server:服务端,earthworm 中未用到这块,服务端是单独 next.js 写的
  • store:状态管理库
  • utils:通用的工具方法
  • app.vue:入口文件
  • nuxt.config.ts
  • tailwind.config.js

可以发现并没有传统 vue 项目的路由文件,因为 nuxt 项目的路由是约定生成的,路由地址基于 page 文件。

具体的文件内容示例,可以参考:

Nest.js

apps 下使用 nest 的脚手架创建后端项目。

shell 复制代码
# 全局安装脚手架
npm i -g @nestjs/cli

# 新建后端项目
nest new api

目录结构

earthworm 中的目录结构和@nestjs/cli 脚手架创建出来的默认项目的目录结构稍微有点不同。

将原本 src 下平铺的 app 相关文件用一个 app 文件夹存放起来,保证了目录结构的一致性。

创建一个接口就是在 src 下创建一个文件夹,例如 course 表示课程相关的接口服务,其中包含了:

  • course.controller.ts
  • course.modules.ts
  • course.service.ts

三者的区别在官方文档中都有详细的介绍。根据我的理解,可以简单的认为,controller 是对外的接口,这里写的接口就是前端需要调用的接口地址,其中接口里需要的实现的一些逻辑,包括对数据库操作的这些逻辑都是封装到 service,controller 里需要调用 service 的方法。module 相当于入口文件,它导入了 controller 和 service,在 app.modules.ts 中就需要导入每个接口的 modules。

service 引用问题

在初尝试编写 nest 接口过程中,遇到过关于 service 相互依赖问题的困扰。在模仿其他接口写法中发现引用的方式有通过 module 也有直接用 service,我最终的感受还是统一用 module 作为入口比较方便清晰关系。

仅供参考。

解决这个问题的第一要则是理清接口间的引用关系,其次就是 modules 中写法。

例如 B.service.ts 中需要引用 A.service.ts 中方法。

那对于 A,modules 中代码如下:

ts 复制代码
import { Module } from "@nestjs/common";

import { AController } from "./A.controller";
import { AService } from "./A.service";

@Module({
  controllers: [AController],
  providers: [AService],
  exports: [AService],
})
export class AModule {}

其中最关键,就是需要在 exports 中导出 service,这样其他的 modules 才可以导入使用。

B.modules 中代码:

ts 复制代码
import { Module } from "@nestjs/common";

import { BController } from "./B.controller";
import { BService } from "./B.service";
import { BModule } from "../A/A.modules";

@Module({
  imports: [AModule],
  controllers: [BController],
  providers: [BService],
})
export class BModule {}

那在 B.service 里就这样,愉快的使用 this.aService 调用其中的方法。

ts 复制代码
import { Injectable } from "@nestjs/common";
import { AService } from "../A/A.service";

@Injectable()
export class CourseService {
  constructor(private readonly aService: AService) {}
}

工程化

earthworm 作为开源项目,要解决多为贡献者提交代码的协同工作。

在 git 钩子中需要做两件事:

  • pre-commit 期间格式化代码
  • commit-msg 期间校验 commit 格式

git hooks 方案使用了 simple-git-hooks 这个库,具体的集成示例可以参考我的这篇文章《更轻量级的 git hooks 方案》,点击查看

安装依赖

安装 simple-git-hooks

shell 复制代码
pnpm add -D -w simple-git-hooks

安装 lint-staged 和 prettier

shell 复制代码
pnpm add -D -w lint-staged prettier

安装变更日志插件

shell 复制代码
pnpm add -D -w conventional-changelog-cli

集成

新建 .simple-git-hooks.js, 代码如下:

js 复制代码
module.exports = {
  "pre-commit": "pnpm exec lint-staged",
  "commit-msg": "pnpm exec tsx ./scripts/verify-commit.ts",
};

新建 .lintstagedrc.mjs,代码如下:

js 复制代码
export default {
  "*.{js,jsx,ts,tsx,mjs,cjs,mts,cts,mtsx,ctsx}": ["prettier --write"],
  "*.{vue,html}": ["prettier --write"],
  "*.{json,md,mdx,yaml}": ["prettier --write"],
  "*.{css,less,sass,scss}": ["prettier --write"],
};

其他更多内容可以参考项目中实现。

最后

欢迎来体验免费使用 earthworm 学习英语,浏览器打开 earthworm.cuixueshe.com/

欢迎参与 earthworm 开源项目,使用的技术栈前沿,淬炼你的技术,作为你面试简历上两点的不二选择。github 仓库地址 github.com/cuixueshe/e...

相关推荐
用户30341319834483 分钟前
Speculative Decoding深度解析:小模型如何加速大模型推理
后端
Dxy12393102165 分钟前
HTML 如何设置 Div 阴影悬浮边框:从基础到进阶
前端·html·css3
好运的阿财6 分钟前
OpenClaw工具拆解之browser+agents_list
前端·人工智能·机器学习·开源软件·ai编程·openclaw·openclaw工具
希望永不加班6 分钟前
SpringBoot 整合 RabbitMQ 入门
java·spring boot·后端·rabbitmq·java-rabbitmq
wan_jm7 分钟前
Go Web 开发提速 5(gos):数据库代码全自动生成 —— 多库统一+零硬编码+极致复用
后端
一叶之政7 分钟前
C++ 系统学习日记・第 06 天|流程结构:循环语句(while+do-while+for 全解 + 嵌套)
后端
JarvanMo13 分钟前
八个开源Flutter应用,让你成为更好的开发者
前端
TE-茶叶蛋15 分钟前
Spring 高级机制:循环依赖 + AOP + @Transactional 失效原理
java·后端·spring
ZC跨境爬虫19 分钟前
Apple官网复刻第二阶段day_2:(前端模块化还原苹果官网WATCH海报)
前端·ui·重构·html·状态模式
Rabbit_QL22 分钟前
【前端基础】npm install 是干嘛的(带参数 vs 不带参数)
前端·npm·node.js