引言
当你第一次打开一个 JS/TS 项目时,可能会被各种配置文件和文件夹搞得一头雾水:
my-project/
├── node_modules/ # 这是什么?为什么这么大?
├── src/ # 源代码放这里?
├── dist/ # 这又是什么?
├── package.json # 听说很重要?
├── package-lock.json # 和上面那个有什么区别?
├── tsconfig.json # TypeScript 配置?
├── . gitignore # Git 相关?
├── .env # 环境变量?
└── README.md # 说明文档
本文将逐一拆解每个部分,让你彻底理解项目结构。
第一部分:项目全景图
一个典型项目的完整结构
my-project/
│
├── 📁 node_modules/ # 第三方依赖包(自动生成,不要手动修改)
│
├── 📁 src/ # 源代码目录
│ ├── index.ts # 入口文件
│ ├── 📁 controllers/ # 控制器(处理请求)
│ ├── 📁 services/ # 服务层(业务逻辑)
│ ├── 📁 models/ # 数据模型
│ ├── 📁 utils/ # 工具函数
│ └── 📁 types/ # TypeScript 类型定义
│
├── 📁 dist/ # 编译输出目录(自动生成)
│
├── 📁 test/ # 测试文件
│
├── 📁 public/ # 静态资源(图片、CSS等)
│
├── 📄 package.json # 🔥 项目配置核心文件
├── 📄 package-lock.json # 依赖版本锁定文件
├── 📄 tsconfig.json # TypeScript 配置
├── 📄 .gitignore # Git 忽略文件配置
├── 📄 .env # 环境变量
├── 📄 . eslintrc.js # 代码规范配置
├── 📄 .prettierrc # 代码格式化配置
└── 📄 README.md # 项目说明文档
文件分类总览
┌─────────────────────────────────────────────────────────┐
│ 项目文件分类 │
├─────────────────────────────────────────────────────────┤
│ │
│ 📦 依赖相关 │
│ • package.json(依赖清单) │
│ • package-lock.json / bun.lockb(版本锁定) │
│ • node_modules/(依赖安装目录) │
│ │
│ 💻 源代码 │
│ • src/(你写的代码) │
│ • dist/(编译后的代码) │
│ │
│ ⚙️ 配置文件 │
│ • tsconfig.json(TypeScript) │
│ • .eslintrc(代码规范) │
│ • .prettierrc(代码格式化) │
│ │
│ 🔐 环境与安全 │
│ • . env(环境变量) │
│ • .gitignore(Git忽略规则) │
│ │
└─────────────────────────────────────────────────────────┘
第二部分:package.json 深度解析
这是什么?
package.json 是项目的身份证和说明书,包含了项目的所有元信息。
完整示例与逐行解释
{
// ============ 基本信息 ============
"name": "my-awesome-api", // 项目名称(小写,可用连字符)
"version": "1.0.0", // 版本号(语义化版本)
"description": "一个很棒的API服务", // 项目描述
"author": "张三 <zhangsan@example.com>", // 作者信息
"license": "MIT", // 开源许可证
// ============ 入口配置 ============
"main": "dist/index.js", // CommonJS 入口(require 时使用)
"module": "dist/index.mjs", // ES Module 入口(import 时使用)
"types": "dist/index.d.ts", // TypeScript 类型定义入口
// ============ 脚本命令 ============
"scripts": {
"dev": "bun --watch src/index.ts", // 开发模式(热重载)
"build": "tsc", // 编译 TypeScript
"start": "node dist/index.js", // 生产环境启动
"test": "bun test", // 运行测试
"lint": "eslint src/", // 代码检查
"format": "prettier --write src/" // 代码格式化
},
// ============ 依赖管理 ============
"dependencies": {
// 生产环境需要的包(部署时会安装)
"express": "^4.18.2",
"cors": "^2. 8.5",
"dotenv": "^16.3.1"
},
"devDependencies": {
// 开发环境需要的包(只在开发时使用)
"typescript": "^5. 3.0",
"@types/express": "^4.17. 21",
"@types/node": "^20.10.0",
"eslint": "^8.55.0",
"prettier": "^3.1.0"
},
// ============ 其他配置 ============
"engines": {
"node": ">=18.0.0" // 要求的 Node.js 版本
},
"keywords": ["api", "express", "typescript"], // 搜索关键词
"repository": {
"type": "git",
"url": "https://github.com/username/repo.git"
}
}
dependencies vs devDependencies
┌─────────────────────────────────────────────────────────┐
│ │
│ dependencies(生产依赖) │
│ ───────────────────── │
│ • 项目运行必需的包 │
│ • 部署到服务器时会安装 │
│ • 例如:express, lodash, axios │
│ │
│ 安装命令: │
│ npm install express │
│ bun add express │
│ │
├─────────────────────────────────────────────────────────┤
│ │
│ devDependencies(开发依赖) │
│ ───────────────────────── │
│ • 只在开发时需要的包 │
│ • 生产环境不会安装 │
│ • 例如:typescript, eslint, jest │
│ │
│ 安装命令: │
│ npm install typescript --save-dev │
│ bun add typescript -d │
│ │
└─────────────────────────────────────────────────────────┘
如何判断放哪里?
|-------|-----------------|-----------------------------|
| 包类型 | 放在哪里 | 示例 |
| 框架/库 | dependencies | express, react, vue |
| 数据库驱动 | dependencies | mysql2, mongoose |
| 工具库 | dependencies | lodash, axios, dayjs |
| 编译工具 | devDependencies | typescript, babel |
| 代码检查 | devDependencies | eslint, prettier |
| 测试框架 | devDependencies | jest, vitest |
| 类型定义 | devDependencies | @types/node, @types/express |
scripts 脚本详解
{
"scripts": {
// 开发相关
"dev": "bun --watch src/index.ts", // 开发模式,文件变化自动重启
"build": "tsc", // 编译 TypeScript 到 JavaScript
"start": "node dist/index.js", // 启动编译后的代码
// 代码质量
"lint": "eslint src/", // 检查代码规范
"lint:fix": "eslint src/ --fix", // 自动修复规范问题
"format": "prettier --write src/", // 格式化代码
// 测试相关
"test": "bun test", // 运行测试
"test:watch": "bun test --watch", // 监听模式运行测试
"test:coverage": "bun test --coverage", // 测试覆盖率
// 部署相关
"clean": "rm -rf dist", // 清理编译目录
"prebuild": "npm run clean", // build 之前自动执行
"postbuild": "echo '构建完成!'" // build 之后自动执行
}
}
特殊脚本前缀:
|--------|-----------|----------------------------|
| 前缀 | 作用 | 示例 |
| pre | 在指定脚本之前执行 | prebuild 在 build 之前执行 |
| post | 在指定脚本之后执行 | postbuild 在 build 之后执行 |
第三部分:node_modules 目录
这是什么?
node_modules 是所有第三方依赖包的安装目录。
node_modules/
├── express/ # 你安装的包
├── lodash/
├── typescript/
└── ... 还有成百上千个包(依赖的依赖)
为什么这么大?
你安装:express(1个包)
↓
express 依赖:body-parser, cookie, debug... (30+个包)
↓
这些包又各自依赖其他包... (100+个包)
↓
最终:node_modules 可能有几百个文件夹,几百MB大小
重要原则
┌─────────────────────────────────────────────────────────┐
│ node_modules 原则 │
├─────────────────────────────────────────────────────────┤
│ │
│ ❌ 不要手动修改 node_modules 中的任何文件 │
│ │
│ ❌ 不要提交到 Git(体积太大,可以随时重新安装) │
│ │
│ ✅ 删除后用 npm install / bun install 重新生成 │
│ │
│ ✅ 在 . gitignore 中添加 node_modules/ │
│ │
└─────────────────────────────────────────────────────────┘
第四部分:锁定文件
package-lock.json / bun.lockb 的作用
问题场景:
package. json 中: "lodash": "^4.17.0"
意味着可以安装 4.17.0 到 4.x. x 的任何版本
张三安装时:可能装了 4.17.20
李四安装时:可能装了 4.17.21
→ 不同版本可能导致 Bug!
解决方案:锁定文件
package-lock.json / bun.lockb 记录了每个包的精确版本
张三安装时:记录 lodash@4.17.20
李四安装时:读取锁定文件,也安装 lodash@4.17.20
→ 团队所有人使用完全相同的版本!
对比
|----------------------|------|----------|---------|
| 文件 | 工具 | 格式 | 特点 |
| package-lock. json | npm | JSON(可读) | 兼容性最好 |
| bun. lockb | Bun | 二进制(不可读) | 速度最快 |
| yarn.lock | Yarn | 文本(可读) | Yarn 专用 |
| pnpm-lock.yaml | pnpm | YAML(可读) | pnpm 专用 |
第五部分:源代码目录结构
src 目录典型结构
src/
├── index.ts # 🚪 入口文件
│
├── 📁 controllers/ # 控制器层:处理 HTTP 请求
│ ├── userController.ts
│ └── productController.ts
│
├── 📁 services/ # 服务层:业务逻辑
│ ├── userService. ts
│ └── productService.ts
│
├── 📁 models/ # 模型层:数据结构定义
│ ├── User.ts
│ └── Product.ts
│
├── 📁 routes/ # 路由层:URL 路径映射
│ ├── userRoutes.ts
│ └── productRoutes.ts
│
├── 📁 middlewares/ # 中间件:请求拦截处理
│ ├── auth.ts
│ └── errorHandler.ts
│
├── 📁 utils/ # 工具函数
│ ├── logger.ts
│ └── helpers.ts
│
├── 📁 config/ # 配置文件
│ └── database.ts
│
└── 📁 types/ # TypeScript 类型定义
└── index. d.ts
分层架构图解
┌─────────────────────────────────────────────────────────┐
│ 请求流程 │
└─────────────────────────────────────────────────────────┘
用户请求
↓
┌───────────────┐
│ Routes │ → 定义 URL 路径,转发给对应 Controller
└───────────────┘
↓
┌───────────────┐
│ Middlewares │ → 身份验证、日志记录、错误处理
└───────────────┘
↓
┌───────────────┐
│ Controllers │ → 接收请求,调用 Service,返回响应
└───────────────┘
↓
┌───────────────┐
│ Services │ → 核心业务逻辑
└───────────────┘
↓
┌───────────────┐
│ Models │ → 数据库操作,数据结构
└───────────────┘
↓
数据库
各层代码示例
1. 入口文件 src/index.ts
import express from 'express';
import cors from 'cors';
import { config } from 'dotenv';
import userRoutes from './routes/userRoutes';
import { errorHandler } from './middlewares/errorHandler';
// 加载环境变量
config();
const app = express();
const PORT = process.env.PORT || 3000;
// 中间件
app. use(cors());
app.use(express.json());
// 路由
app.use('/api/users', userRoutes);
// 错误处理(放在最后)
app.use(errorHandler);
// 启动服务器
app.listen(PORT, () => {
console.log(`🚀 服务器运行在 http://localhost:${PORT}`);
});
2. 路由 src/routes/userRoutes.ts
import { Router } from 'express';
import { UserController } from '../controllers/userController';
import { authMiddleware } from '../middlewares/auth';
const router = Router();
const userController = new UserController();
// 公开路由
router.post('/register', userController.register);
router.post('/login', userController.login);
// 需要登录的路由
router.get('/profile', authMiddleware, userController.getProfile);
router.put('/profile', authMiddleware, userController. updateProfile);
export default router;
3. 控制器 src/controllers/userController.ts
import { Request, Response } from 'express';
import { UserService } from '../services/userService';
export class UserController {
private userService = new UserService();
// 用户注册
register = async (req: Request, res: Response) => {
try {
const { email, password, name } = req. body;
const user = await this. userService.createUser({ email, password, name });
res. status(201).json({ success: true, data: user });
} catch (error) {
res.status(400).json({ success: false, message: error.message });
}
};
// 用户登录
login = async (req: Request, res: Response) => {
try {
const { email, password } = req. body;
const token = await this.userService.login(email, password);
res.json({ success: true, token });
} catch (error) {
res.status(401).json({ success: false, message: '登录失败' });
}
};
// 获取用户信息
getProfile = async (req: Request, res: Response) => {
const userId = req.user. id; // 从中间件获取
const user = await this.userService. getUserById(userId);
res.json({ success: true, data: user });
};
}
4. 服务层 src/services/userService.ts
import { User } from '../models/User';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
export class UserService {
// 创建用户
async createUser(data: { email: string; password: string; name: string }) {
// 检查邮箱是否已存在
const existingUser = await User.findOne({ email: data.email });
if (existingUser) {
throw new Error('邮箱已被注册');
}
// 密码加密
const hashedPassword = await bcrypt.hash(data.password, 10);
// 创建用户
const user = await User.create({
... data,
password: hashedPassword,
});
return { id: user. id, email: user.email, name: user.name };
}
// 用户登录
async login(email: string, password: string) {
const user = await User. findOne({ email });
if (!user) {
throw new Error('用户不存在');
}
const isValid = await bcrypt. compare(password, user.password);
if (!isValid) {
throw new Error('密码错误');
}
// 生成 JWT Token
const token = jwt.sign(
{ id: user.id, email: user.email },
process.env. JWT_SECRET!,
{ expiresIn: '7d' }
);
return token;
}
// 根据 ID 获取用户
async getUserById(id: string) {
return User.findById(id). select('-password');
}
}
5. 数据模型 src/models/User.ts
// 定义用户数据结构和类型
export interface IUser {
id: string;
email: string;
password: string;
name: string;
createdAt: Date;
updatedAt: Date;
}
// 如果使用 MongoDB + Mongoose
import mongoose, { Schema } from 'mongoose';
const userSchema = new Schema<IUser>(
{
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
name: { type: String, required: true },
},
{ timestamps: true } // 自动添加 createdAt 和 updatedAt
);
export const User = mongoose. model<IUser>('User', userSchema);
6. 中间件 src/middlewares/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
// 从 Header 获取 Token
const authHeader = req.headers. authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401). json({ message: '未提供认证令牌' });
}
const token = authHeader. split(' ')[1];
try {
// 验证 Token
const decoded = jwt.verify(token, process.env.JWT_SECRET! );
req. user = decoded; // 将用户信息挂载到请求对象
next();
} catch (error) {
return res.status(401).json({ message: '令牌无效或已过期' });
}
};
7. 工具函数 src/utils/logger.ts
// 简单的日志工具
export const logger = {
info: (message: string, data?: any) => {
console.log(`[INFO] ${new Date().toISOString()} - ${message}`, data || '');
},
error: (message: string, error?: any) => {
console.error(`[ERROR] ${new Date().toISOString()} - ${message}`, error || '');
},
warn: (message: string, data?: any) => {
console.warn(`[WARN] ${new Date().toISOString()} - ${message}`, data || '');
},
};
第六部分:配置文件详解
tsconfig.json(TypeScript 配置)
{
"compilerOptions": {
// 编译目标
"target": "ES2022", // 编译成哪个版本的 JS
"module": "commonjs", // 模块系统
"lib": ["ES2022"], // 可用的类型库
// 输出配置
"outDir": "./dist", // 编译输出目录
"rootDir": "./src", // 源代码目录
// 严格模式
"strict": true, // 启用所有严格检查
"noImplicitAny": true, // 禁止隐式 any 类型
// 模块解析
"esModuleInterop": true, // 兼容 CommonJS 模块
"resolveJsonModule": true, // 允许导入 JSON
// 其他
"skipLibCheck": true, // 跳过库文件类型检查(加速编译)
"forceConsistentCasingInFileNames": true // 强制文件名大小写一致
},
"include": ["src/**/*"], // 要编译的文件
"exclude": ["node_modules", "dist"] // 排除的目录
}
.gitignore(Git 忽略配置)
# 依赖目录
node_modules/
# 编译输出
dist/
build/
# 环境变量(包含敏感信息)
.env
. env.local
. env.*. local
# 日志文件
logs/
*.log
npm-debug.log*
# 操作系统文件
.DS_Store
Thumbs.db
# IDE 配置
.idea/
.vscode/
*.swp
*.swo
# 测试覆盖率
coverage/
# 临时文件
tmp/
temp/
.env(环境变量)
# 服务器配置
PORT=3000
NODE_ENV=development
# 数据库配置
DATABASE_URL=mongodb://localhost:27017/myapp
REDIS_URL=redis://localhost:6379
# 认证密钥(绝对不要提交到 Git!)
JWT_SECRET=your-super-secret-key-here
API_KEY=your-api-key
# 第三方服务
SMTP_HOST=smtp. example.com
SMTP_USER=user@example. com
SMTP_PASS=password
在代码中使用环境变量:
import { config } from 'dotenv';
config(); // 加载 .env 文件
// 使用环境变量
const port = process.env. PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
const jwtSecret = process. env.JWT_SECRET;
第七部分:dist 目录
这是什么?
dist(distribution 的缩写)是编译/打包后的输出目录。
编译前(TypeScript) 编译后(JavaScript)
src/ dist/
├── index.ts → ├── index. js
├── utils/ ├── utils/
│ └── helper.ts → │ └── helper.js
└── types/ └── (类型文件不输出)
└── index. d.ts
为什么需要编译?
┌─────────────────────────────────────────────────────────┐
│ │
│ TypeScript (. ts) │
│ ───────────────── │
│ • 有类型检查 │
│ • 有现代语法 │
│ • 浏览器/Node.js 不能直接运行 │
│ │
│ ↓ 编译 (tsc) │
│ │
│ JavaScript (.js) │
│ ───────────────── │
│ • 浏览器/Node.js 可以直接运行 │
│ • 可以部署到生产环境 │
│ │
└─────────────────────────────────────────────────────────┘
Bun 的特殊之处
# Node.js 运行 TypeScript
tsc # 先编译
node dist/index.js # 再运行
# Bun 直接运行 TypeScript(内部自动编译)
bun src/index.ts # 一步到位!
第八部分:从零创建完整项目
实践:创建一个 API 项目
# 1. 创建项目目录
mkdir my-api
cd my-api
# 2. 初始化项目
bun init -y
# 3. 安装依赖
bun add express cors dotenv
bun add -d typescript @types/node @types/express @types/cors
# 4. 初始化 TypeScript
bunx tsc --init
# 5. 创建目录结构
mkdir -p src/{controllers,services,models,routes,middlewares,utils,config}
# 6. 创建必要文件
touch src/index.ts
touch . env
touch . gitignore
最终目录结构
my-api/
├── node_modules/
├── src/
│ ├── index.ts
│ ├── controllers/
│ ├── services/
│ ├── models/
│ ├── routes/
│ ├── middlewares/
│ ├── utils/
│ └── config/
├── package.json
├── bun.lockb
├── tsconfig.json
├── .env
├── .gitignore
└── README. md
配置 package.json scripts
{
"scripts": {
"dev": "bun --watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"lint": "eslint src/",
"test": "bun test"
}
}
总结
核心文件速记表
|---------------------|---------------|----------|
| 文件/目录 | 作用 | 提交到 Git? |
| package.json | 项目配置核心 | ✅ |
| package-lock.json | 依赖版本锁定 | ✅ |
| tsconfig.json | TypeScript 配置 | ✅ |
| . gitignore | Git 忽略规则 | ✅ |
| .env | 环境变量(敏感信息) | ❌ |
| node_modules/ | 依赖安装目录 | ❌ |
| dist/ | 编译输出目录 | ❌ |
| src/ | 源代码目录 | ✅ |
分层架构速记
Routes → Middlewares → Controllers → Services → Models → Database
↓ ↓ ↓ ↓ ↓
URL映射 请求拦截 处理请求 业务逻辑 数据操作
新手建议
- 先跑起来:不要纠结完美的目录结构,先让项目能运行
- 逐步重构:代码多了再拆分目录
- 看优秀项目:GitHub 上找 Star 多的项目学习结构
- 保持一致:团队内统一规范比"最佳实践"更重要
现在你已经理解了 JS/TS 项目的完整结构,可以自信地开始开发了!🚀
最后更新:2025 年 12 月