搬瓦工 VPS 搭建 GraphQL API 服务教程
GraphQL 是由 Facebook 开发的 API 查询语言,允许客户端精确指定需要的数据字段,一次请求获取多个资源,有效避免了 REST API 中常见的过度获取和多次请求问题。本教程将介绍如何在搬瓦工 VPS 上使用 Node.js 和 Apollo Server 搭建生产级 GraphQL API 服务,包括 Schema 设计、数据库集成和 Docker 部署。部署前请确保已安装好 Docker 和 Docker Compose。
一、GraphQL 核心概念
- Schema:定义 API 的数据类型和操作,包括 Query(查询)、Mutation(变更)和 Subscription(订阅)。
- Resolver:解析器函数,为 Schema 中的每个字段提供数据获取逻辑。
- Type System:强类型系统,所有数据必须符合预定义的类型。
- Introspection:自省能力,客户端可以查询 Schema 结构,便于文档生成和开发工具集成。
二、项目初始化
mkdir -p /opt/graphql-api && cd /opt/graphql-api
# 初始化 Node.js 项目
cat > package.json <<'EOF'
{
"name": "graphql-api-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js"
},
"dependencies": {
"@apollo/server": "^4.10.0",
"graphql": "^16.8.0",
"pg": "^8.12.0",
"dataloader": "^2.2.2",
"graphql-scalars": "^1.23.0"
}
}
EOF
三、定义 GraphQL Schema
mkdir -p src
cat > src/schema.js <<'EOF'
export const typeDefs = `#graphql
scalar DateTime
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
comments: [Comment!]!
createdAt: DateTime!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
posts(published: Boolean, limit: Int, offset: Int): [Post!]!
searchPosts(keyword: String!): [Post!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
publishPost(id: ID!): Post!
addComment(input: AddCommentInput!): Comment!
}
input CreateUserInput {
name: String!
email: String!
age: Int
}
input UpdateUserInput {
name: String
email: String
age: Int
}
input CreatePostInput {
title: String!
content: String!
authorId: ID!
}
input AddCommentInput {
text: String!
postId: ID!
authorId: ID!
}
type Subscription {
postPublished: Post!
commentAdded(postId: ID!): Comment!
}
`;
EOF
四、实现 Resolver
cat > src/resolvers.js <<'EOF'
export const resolvers = {
Query: {
user: async (_, { id }, { db }) => {
const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
return result.rows[0];
},
users: async (_, { limit = 20, offset = 0 }, { db }) => {
const result = await db.query(
'SELECT * FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2',
[limit, offset]
);
return result.rows;
},
post: async (_, { id }, { db }) => {
const result = await db.query('SELECT * FROM posts WHERE id = $1', [id]);
return result.rows[0];
},
posts: async (_, { published, limit = 20, offset = 0 }, { db }) => {
let query = 'SELECT * FROM posts';
const params = [];
if (published !== undefined) {
query += ' WHERE published = $1';
params.push(published);
}
query += ` ORDER BY created_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`;
params.push(limit, offset);
const result = await db.query(query, params);
return result.rows;
},
},
User: {
posts: async (parent, _, { loaders }) => {
return loaders.postsByUser.load(parent.id);
},
},
Post: {
author: async (parent, _, { loaders }) => {
return loaders.user.load(parent.author_id);
},
comments: async (parent, _, { loaders }) => {
return loaders.commentsByPost.load(parent.id);
},
},
Mutation: {
createUser: async (_, { input }, { db }) => {
const { name, email, age } = input;
const result = await db.query(
'INSERT INTO users (name, email, age) VALUES ($1, $2, $3) RETURNING *',
[name, email, age]
);
return result.rows[0];
},
createPost: async (_, { input }, { db }) => {
const { title, content, authorId } = input;
const result = await db.query(
'INSERT INTO posts (title, content, author_id) VALUES ($1, $2, $3) RETURNING *',
[title, content, authorId]
);
return result.rows[0];
},
},
};
EOF
五、服务入口文件
cat > src/index.js <<'EOF'
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import pg from 'pg';
import { typeDefs } from './schema.js';
import { resolvers } from './resolvers.js';
const pool = new pg.Pool({
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'graphql_db',
user: process.env.DB_USER || 'graphql_user',
password: process.env.DB_PASS || 'graphql_pass_2026',
max: 20,
});
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
});
const { url } = await startStandaloneServer(server, {
listen: { port: parseInt(process.env.PORT || '4000') },
context: async ({ req }) => ({
db: pool,
token: req.headers.authorization,
}),
});
console.log(`GraphQL server ready at ${url}`);
EOF
六、Docker Compose 部署
cat > Dockerfile <<'EOF'
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN npm install --production
COPY src/ ./src/
EXPOSE 4000
CMD ["node", "src/index.js"]
EOF
cat > docker-compose.yml <<'EOF'
version: '3.8'
services:
graphql:
build: .
container_name: graphql-api
restart: always
ports:
- "127.0.0.1:4000:4000"
environment:
NODE_ENV: production
PORT: "4000"
DB_HOST: postgres
DB_NAME: graphql_db
DB_USER: graphql_user
DB_PASS: graphql_pass_2026
depends_on:
- postgres
postgres:
image: postgres:15-alpine
container_name: graphql-db
restart: always
environment:
POSTGRES_DB: graphql_db
POSTGRES_USER: graphql_user
POSTGRES_PASSWORD: graphql_pass_2026
volumes:
- pgdata:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
pgdata:
EOF
docker compose up -d
七、查询示例
# 查询用户及其文章
curl -X POST http://localhost:4000/graphql \
-H 'Content-Type: application/json' \
-d '{"query": "{ users(limit: 5) { id name email posts { title published } } }"}'
# 创建用户
curl -X POST http://localhost:4000/graphql \
-H 'Content-Type: application/json' \
-d '{"query": "mutation { createUser(input: { name: \"Alice\", email: \"alice@example.com\", age: 28 }) { id name } }"}'
八、生产优化
- DataLoader:使用 DataLoader 解决 N+1 查询问题,批量加载关联数据。
- 查询深度限制:防止恶意深度嵌套查询消耗过多资源。
- 持久化查询:预编译查询语句,减少解析开销并提升安全性。
- 缓存:利用 Apollo Cache 或 Redis 缓存热点查询结果。
九、常见问题
N+1 查询问题
当查询嵌套关联数据时会产生大量数据库查询,使用 DataLoader 进行批量加载可以有效解决此问题。
生产环境禁用 Introspection
在生产环境中应禁用 Schema 自省功能,防止 API 结构被外部探测。设置环境变量 NODE_ENV=production 即可自动禁用。
总结
GraphQL 是构建灵活 API 的现代化方案,特别适合前端驱动的应用开发。配合 Swagger 文档和 Postman 测试可以建立完整的 API 开发工作流。选购搬瓦工 VPS 请参考 全部方案,购买时使用优惠码 NODESEEK2026 可享受 6.77% 的折扣,购买链接:bwh81.net。