05-JavaScript/TypeScript 项目结构完全解析

引言

当你第一次打开一个 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 | 在指定脚本之前执行 | prebuildbuild 之前执行 |
| post | 在指定脚本之后执行 | postbuildbuild 之后执行 |


第三部分: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映射    请求拦截      处理请求     业务逻辑    数据操作

新手建议

  1. 先跑起来:不要纠结完美的目录结构,先让项目能运行
  2. 逐步重构:代码多了再拆分目录
  3. 看优秀项目:GitHub 上找 Star 多的项目学习结构
  4. 保持一致:团队内统一规范比"最佳实践"更重要

现在你已经理解了 JS/TS 项目的完整结构,可以自信地开始开发了!🚀


最后更新:2025 年 12 月

相关推荐
m0_740043731 小时前
父组件 子组件
javascript·vue.js·ecmascript
渴望成为python大神的前端小菜鸟1 小时前
JS宏任务 & 微任务 API 速查表
javascript
前端小王呀1 小时前
自定义图表相关配置
android·前端·javascript
chilavert3181 小时前
技术演进中的开发沉思-226 Ajax:前端兼容
javascript·架构
西西学代码1 小时前
flutter---进度条
前端·javascript·flutter
小简GoGo1 小时前
新手如何搭建配置Android Studio并成功运行React Native项目(使用自带的虚拟机运行)
react native·react.js·android studio
时间的情敌1 小时前
Vue3 和 Vue2 的核心区别
前端·javascript·vue.js
Aevget1 小时前
DevExtreme JS & ASP.NET Core v25.2新功能预览 - 提升AI扩展功能
javascript·人工智能·ui·asp.net·界面控件·devextreme