从0到1构建企业级GraphQL全栈应用:Apollo+Express+PostgreSQL实战指南

从0到1构建企业级GraphQL全栈应用:Apollo+Express+PostgreSQL实战指南

【免费下载链接】fullstack-apollo-express-postgresql-boilerplate 💥 A sophisticated GraphQL with Apollo, Express and PostgreSQL boilerplate project. 【免费下载链接】fullstack-apollo-express-postgresql-boilerplate 项目地址: https://gitcode.com/gh_mirrors/fu/fullstack-apollo-express-postgresql-boilerplate

你还在为全栈项目架构选型烦恼?还在手动编写CRUD接口浪费时间?本文将带你使用fullstack-apollo-express-postgresql-boilerplate构建生产级GraphQL应用,掌握现代API开发的新范式。读完本文你将获得:

  • 从零搭建Apollo Server后端服务的完整流程
  • 实现JWT认证与权限控制的最佳实践
  • PostgreSQL数据库设计与Sequelize ORM应用
  • 高效数据加载与订阅功能的实现方案
  • 企业级项目的测试与部署策略

项目架构概览

技术栈选型解析

fullstack-apollo-express-postgresql-boilerplate是一个集成了Apollo、Express和PostgreSQL的全栈解决方案,专为构建高性能GraphQL应用设计。其核心技术栈包括:

技术组件版本作用优势
Apollo Server^2.2.3GraphQL服务器支持订阅、类型安全、集成Express
Express^4.16.4Web框架轻量灵活,中间件生态丰富
PostgreSQL-关系型数据库强一致性,JSON支持,事务可靠
Sequelize^4.41.2ORM工具数据模型定义,关联查询,迁移管理
JWT^8.4.0认证机制无状态,跨服务,易于扩展
DataLoader^1.4.0数据加载批量查询,缓存优化,N+1问题解决

项目目录结构

src/
├── index.js              # 应用入口点
├── loaders/              # 数据加载器
│   ├── index.js
│   └── user.js
├── models/               # 数据模型定义
│   ├── index.js
│   ├── message.js
│   └── user.js
├── resolvers/            # GraphQL解析器
│   ├── authorization.js  # 权限控制
│   ├── index.js
│   ├── message.js
│   └── user.js
├── schema/               # GraphQL类型定义
│   ├── index.js
│   ├── message.js
│   └── user.js
├── subscription/         # 订阅功能
│   ├── index.js
│   └── message.js
└── tests/                # 测试文件
    ├── api.js
    └── user.spec.js

核心功能模块

该样板项目提供了企业级应用所需的完整功能集:

mermaid

  • 认证授权:基于JWT的登录注册,角色权限控制
  • 数据模型:用户(User)和消息(Message)实体及关联
  • GraphQL API:查询、变更和订阅操作完整实现
  • 性能优化:DataLoader批量查询与缓存
  • 错误处理:统一错误格式,验证信息提取

环境搭建与初始化

前置依赖安装

在开始前,请确保系统已安装以下依赖:

  • Node.js (v10.11.0或兼容版本)
  • npm (通常随Node.js安装)
  • PostgreSQL (9.6或更高版本)

项目获取与安装

# 克隆仓库
git clone https://gitcode.com/gh_mirrors/fu/fullstack-apollo-express-postgresql-boilerplate

# 进入项目目录
cd fullstack-apollo-express-postgresql-boilerplate

# 安装依赖
npm install

环境变量配置

创建.env文件并添加以下内容:

# 数据库配置
DATABASE=mydatabase
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres

# 认证密钥(随机字符串)
SECRET=your_secure_random_string_here.2342.dawasdq

注意:生产环境中,SECRET应使用强随机字符串,并妥善保管,不要提交到版本控制系统。

数据库初始化

使用PostgreSQL命令行工具创建数据库:

# 登录PostgreSQL
psql -U postgres

# 创建数据库
CREATE DATABASE mydatabase;

# 退出
\q

数据模型设计

实体关系模型

项目包含两个核心实体:User(用户)和Message(消息),它们之间是一对多关系:

mermaid

用户模型实现

src/models/user.js定义了用户数据模型及其行为:

import bcrypt from 'bcrypt';

const user = (sequelize, DataTypes) => {
  const User = sequelize.define('user', {
    username: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false,
      validate: {
        notEmpty: true,
      },
    },
    email: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false,
      validate: {
        notEmpty: true,
        isEmail: true,
      },
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        notEmpty: true,
        len: [7, 42], // 密码长度限制
      },
    },
    role: {
      type: DataTypes.STRING,
    },
  });

  // 关联定义:一个用户拥有多条消息
  User.associate = models => {
    User.hasMany(models.Message, { onDelete: 'CASCADE' });
  };

  // 自定义查询方法:通过用户名或邮箱查找用户
  User.findByLogin = async login => {
    let user = await User.findOne({
      where: { username: login },
    });

    if (!user) {
      user = await User.findOne({
        where: { email: login },
      });
    }

    return user;
  };

  // 密码加密:创建前自动执行
  User.beforeCreate(async user => {
    user.password = await user.generatePasswordHash();
  });

  // 密码哈希生成
  User.prototype.generatePasswordHash = async function() {
    const saltRounds = 10; // 盐值强度
    return await bcrypt.hash(this.password, saltRounds);
  };

  // 密码验证方法
  User.prototype.validatePassword = async function(password) {
    return await bcrypt.compare(password, this.password);
  };

  return User;
};

export default user;

消息模型实现

src/models/message.js定义了消息数据模型:

const message = (sequelize, DataTypes) => {
  const Message = sequelize.define('message', {
    text: {
      type: DataTypes.STRING,
      validate: { notEmpty: true },
    },
  });

  // 关联定义:消息属于某个用户
  Message.associate = models => {
    Message.belongsTo(models.User);
  };

  return Message;
};

export default message;

GraphQL API设计

类型定义

项目采用模块化方式组织GraphQL类型定义,src/schema/index.js整合了所有类型:

import { gql } from 'apollo-server-express';

import userSchema from './user';
import messageSchema from './message';

const linkSchema = gql`
  scalar Date

  type Query {
    _: Boolean
  }

  type Mutation {
    _: Boolean
  }

  type Subscription {
    _: Boolean
  }
`;

export default [linkSchema, userSchema, messageSchema];

用户相关操作

用户解析器src/resolvers/user.js实现了用户相关的GraphQL操作:

查询操作
Query: {
  // 获取所有用户(需管理员权限)
  users: async (parent, args, { models }) => {
    return await models.User.findAll();
  },
  
  // 获取单个用户
  user: async (parent, { id }, { models }) => {
    return await models.User.findById(id);
  },
  
  // 获取当前登录用户
  me: async (parent, args, { models, me }) => {
    if (!me) {
      return null;
    }
    return await models.User.findById(me.id);
  },
}
变更操作
Mutation: {
  // 用户注册
  signUp: async (
    parent,
    { username, email, password },
    { models, secret },
  ) => {
    const user = await models.User.create({
      username,
      email,
      password, // 将自动加密
    });

    return { token: createToken(user, secret, '30m') };
  },

  // 用户登录
  signIn: async (
    parent,
    { login, password },
    { models, secret },
  ) => {
    // 支持用户名或邮箱登录
    const user = await models.User.findByLogin(login);

    if (!user) {
      throw new UserInputError('No user found with this login credentials.');
    }

    // 验证密码
    const isValid = await user.validatePassword(password);
    if (!isValid) {
      throw new AuthenticationError('Invalid password.');
    }

    // 生成JWT令牌
    return { token: createToken(user, secret, '30m') };
  },

  // 更新用户信息(需认证)
  updateUser: combineResolvers(
    isAuthenticated,
    async (parent, { username }, { models, me }) => {
      const user = await models.User.findById(me.id);
      return await user.update({ username });
    },
  ),

  // 删除用户(需管理员权限)
  deleteUser: combineResolvers(
    isAdmin,
    async (parent, { id }, { models }) => {
      return await models.User.destroy({ where: { id } });
    },
  ),
}

消息相关操作

消息解析器src/resolvers/message.js实现了消息的CRUD和订阅功能:

分页查询实现
Query: {
  messages: async (parent, { cursor, limit = 100 }, { models }) => {
    // 游标分页条件
    const cursorOptions = cursor
      ? {
          where: {
            createdAt: {
              [Sequelize.Op.lt]: fromCursorHash(cursor),
            },
          },
        }
      : {};

    // 查询消息,按创建时间降序
    const messages = await models.Message.findAll({
      order: [['createdAt', 'DESC']],
      limit: limit + 1, // 查询多一条用于判断是否有下一页
      ...cursorOptions,
    });

    // 处理分页信息
    const hasNextPage = messages.length > limit;
    const edges = hasNextPage ? messages.slice(0, -1) : messages;

    return {
      edges,
      pageInfo: {
        hasNextPage,
        endCursor: toCursorHash(
          edges[edges.length - 1].createdAt.toString(),
        ),
      },
    };
  },
}
订阅功能
Subscription: {
  messageCreated: {
    subscribe: () => pubsub.asyncIterator(EVENTS.MESSAGE.CREATED),
  },
}

认证与权限控制

JWT认证流程

项目采用JWT(JSON Web Token)实现无状态认证:

mermaid

认证实现位于src/index.js

const getMe = async req => {
  const token = req.headers['x-token'];
  
  if (token) {
    try {
      return await jwt.verify(token, process.env.SECRET);
    } catch (e) {
      throw new AuthenticationError('Your session expired. Sign in again.');
    }
  }
};

权限控制实现

src/resolvers/authorization.js定义了权限检查函数:

import { AuthenticationError, ForbiddenError } from 'apollo-server';

export const isAuthenticated = (parent, args, { me }) => {
  if (!me) {
    throw new AuthenticationError('You must be logged in');
  }
};

export const isAdmin = combineResolvers(
  isAuthenticated,
  (parent, args, { me: { role } }) => {
    if (role !== 'ADMIN') {
      throw new ForbiddenError('Not authorized as admin');
    }
  },
);

export const isMessageOwner = async (parent, { id }, { models, me }) => {
  const message = await models.Message.findById(id);
  
  if (message.userId !== me.id) {
    throw new ForbiddenError('Not authorized as message owner');
  }
};

使用权限控制示例:

// 需管理员权限的操作
deleteUser: combineResolvers(
  isAdmin,
  async (parent, { id }, { models }) => {
    return await models.Message.destroy({ where: { id } });
  },
)

性能优化

DataLoader实现

DataLoader用于解决GraphQL查询中的N+1问题,src/loaders/user.js实现了用户数据加载器:

exports.batchUsers = async (keys, models) => {
  const users = await models.User.findAll({
    where: {
      id: keys,
    },
  });
  
  return keys.map(key => users.find(user => user.id === key));
};

在上下文中配置DataLoader:

context: async ({ req, connection }) => {
  // ...
  return {
    models,
    me,
    secret: process.env.SECRET,
    loaders: {
      user: new DataLoader(keys =>
        loaders.user.batchUsers(keys, models),
      ),
    },
  };
}

使用DataLoader加载关联数据:

Message: {
  user: async (message, args, { loaders }) => {
    return await loaders.user.load(message.userId);
  },
}

测试策略

测试环境配置

package.json中配置测试脚本:

"scripts": {
  "test:run-server": "TEST_DATABASE=mytestdatabase npm start",
  "test:execute-test": "mocha --require @babel/register 'src/**/*.spec.js'"
}

测试数据库设置

# 创建测试数据库
createdb mytestdatabase

测试执行流程

# 终端1:启动测试服务器
npm run test:run-server

# 终端2:执行测试用例
npm run test:execute-test

部署指南

环境变量配置

生产环境需配置以下环境变量:

DATABASE_URL=postgres://username:password@hostname:port/database
PORT=8000
SECRET=your_secure_random_string
NODE_ENV=production

启动命令

# 使用生产模式启动
npm start

应用示例

注册用户

mutation SignUp($username: String!, $email: String!, $password: String!) {
  signUp(username: $username, email: $email, password: $password) {
    token
  }
}

变量:

{
  "username": "johndoe",
  "email": "john@example.com",
  "password": "securepassword"
}

创建消息

mutation CreateMessage($text: String!) {
  createMessage(text: $text) {
    id
    text
    createdAt
    user {
      username
    }
  }
}

订阅消息

subscription {
  messageCreated {
    message {
      id
      text
      user {
        username
      }
    }
  }
}

总结与展望

本教程详细介绍了fullstack-apollo-express-postgresql-boilerplate的使用方法,包括环境搭建、数据模型设计、API实现、认证授权、性能优化和部署等方面。通过这个样板项目,你可以快速构建企业级GraphQL应用,避免重复开发基础功能。

未来可以考虑扩展的方向:

  • 添加更多数据验证规则
  • 实现更细粒度的权限控制
  • 添加文件上传功能
  • 集成Redis实现缓存
  • 实现API速率限制

希望这篇指南能帮助你更好地理解和使用GraphQL技术栈。如有任何问题或建议,欢迎在项目仓库提交issue或PR。

如果你觉得本教程对你有帮助,请点赞、收藏并关注作者,获取更多全栈开发技术分享!

【免费下载链接】fullstack-apollo-express-postgresql-boilerplate 💥 A sophisticated GraphQL with Apollo, Express and PostgreSQL boilerplate project. 【免费下载链接】fullstack-apollo-express-postgresql-boilerplate 项目地址: https://gitcode.com/gh_mirrors/fu/fullstack-apollo-express-postgresql-boilerplate

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值