如何搭建 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...

相关推荐
小码哥_常5 小时前
Spring Boot 牵手Spring AI,玩转DeepSeek大模型
后端
freewlt5 小时前
前端性能优化实战:从 Lighthouse 分数到用户体验的全面升级
前端·性能优化·ux
0xDevNull5 小时前
Java反射机制深度解析:从原理到实战
java·开发语言·后端
小小亮016 小时前
Next.js基础
开发语言·前端·javascript
华洛6 小时前
我用AI做了一个48秒的真人精品漫剧,不难也不贵
前端·javascript·后端
WZTTMoon6 小时前
Spring Boot 中Servlet、Filter、Listener 四种注册方式全解析
spring boot·后端·servlet
standovon6 小时前
Spring Boot整合Redisson的两种方式
java·spring boot·后端
Novlan16 小时前
我把 Claude Code 里的隐藏彩蛋提取出来了——零依赖的 ASCII 虚拟宠物系统
前端
Cosolar7 小时前
LlamaIndex RAG 本地部署+API服务,快速搭建一个知识库检索助手
后端·openai·ai编程
IAUTOMOBILE7 小时前
Python 流程控制与函数定义:从调试现场到工程实践
java·前端·python