Spring Boot + GraphQL API 实战:使用 React 和 Auth0 构建安全数据平台
GraphQL API 能够通过对象间的引用,在单一响应中返回所有相关数据。
本教程将逐步演示如何使用 Spring Boot 和 Spring for GraphQL 构建一个 GraphQL API,以查询 Neo4j 数据库中的公司、人员和属性数据。同时,我们还将展示如何使用 Next.js 和 MUI 数据网格构建一个 React 客户端来消费该 API。整个项目的客户端和服务器均通过 Auth0 进行身份验证,服务器使用 Okta Spring Boot Starter,而客户端则使用 Auth0 React SDK。
使用 Spring Boot 构建 GraphQL API
初始化 Spring Boot 项目
资源服务器是一个基于 Spring Boot 的 Web 应用程序,它通过 Spring for GraphQL 提供 GraphQL API。该 API 使用 Spring Data Neo4j 查询 Neo4j 数据库中的公司及其相关人员和属性信息。
通过 Spring Initializr 和 HTTPie 创建项目:
https start.spring.io/starter.zip
bootVersion==3.2.1
language==java
packaging==jar
javaVersion==17
type==gradle-project
dependencies==data-neo4j,graphql,docker-compose,web
groupId==com.okta.developer
artifactId==spring-graphql
name=="Spring Boot API"
description=="Demo project of a Spring Boot GraphQL API"
packageName==com.okta.developer.demo > spring-graphql-api.zip
解压项目文件后,开始编辑项目。在 src/main/resources/graphql 目录下创建名为 [schema](https://www.explinks.com/blog/ua-what-is-schema-in-database/).graphqls 的文件,定义 GraphQL API 的架构:
type Query {
companyList(page: Int): [Company!]!
companyCount: Int
}
type Company {
id: ID
SIC: String
category: String
companyNumber: String
countryOfOrigin: String
incorporationDate: String
mortgagesOutstanding: Int
name: String
status: String
controlledBy: [Person!]!
owners: [Property!]!
}type Person {
id: ID
birthMonth: String
birthYear: String
nationality: String
name: String
countryOfResidence: String
}type Property {
id: ID
address: String
county: String
district: String
titleNumber: String
}
定义领域模型
在 src/main/java 下创建包 com.okta.developer.demo.domain,并添加以下类:
Person 类
package com.okta.developer.demo.domain;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;@Node
public class Person {
@Id
@GeneratedValue
private Long id;
private String birthMonth;
private String birthYear;
private String countryOfResidence;
private String name;
private String nationality; // 构造函数、getter 和 setter 方法
}
Property 类
package com.okta.developer.demo.domain;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;@Node
public class Property {
@Id
@GeneratedValue
private Long id;
private String address;
private String county;
private String district;
private String titleNumber; // 构造函数、getter 和 setter 方法
}
Company 类
package com.okta.developer.demo.domain;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;@Node
public class Company {
@Id
@GeneratedValue
private Long id;
private String SIC;
private String category;
private String companyNumber;
private String countryOfOrigin;
private LocalDate incorporationDate;
private Integer mortgagesOutstanding;
private String name;
private String status; @Relationship(type = "HAS_CONTROL", direction = Relationship.Direction.INCOMING)
private List controlledBy = new ArrayList(); private List owns = new ArrayList(); // 构造函数、getter 和 setter 方法
}
创建 Repository 和 Controller
CompanyRepository
package com.okta.developer.demo.repository;
import com.okta.developer.demo.domain.Company;
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;public interface CompanyRepository extends ReactiveNeo4jRepository {
}
CompanyController
package com.okta.developer.demo.controller;
import com.okta.developer.demo.domain.Company;
import com.okta.developer.demo.repository.CompanyRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;@Controller
public class CompanyController {
@Autowired
private CompanyRepository companyRepository; @QueryMapping
public Flux companyList(@Argument Long page) {
return companyRepository.findAll().skip(page * 10).take(10);
} @QueryMapping
public Mono companyCount() {
return companyRepository.count();
}
}
构建 React 客户端
初始化 Next.js 项目
在 Spring Boot 项目的父目录下运行以下命令:
npx create-next-app@latest react-graphql
根据提示配置项目,并安装必要的依赖项:
cd react-graphql
npm install @mui/x-data-grid @mui/material @emotion/react @emotion/styled axios
创建 API 客户端
在 src/services 目录下创建 base.tsx 文件:
import axios from 'axios';
export const backendAPI = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_SERVER_URL,
});export default backendAPI;
添加 companies.tsx 文件:
import { backendAPI } from './base';
export const CompanyApi = {
getCompanyList: async (page: number) => {
const response = await backendAPI.post('/graphql', {
query: { companyList(page: ${page}) { id name category } },
});
return response.data.data.companyList;
},
};
创建数据表组件
在 src/components/company 目录下创建 CompanyTable.tsx:
import { DataGrid } from '@mui/x-data-grid';
const CompanyTable = ({ rows, columns }) => {
return ;
};export default CompanyTable;
使用 Auth0 添加安全性
配置 Auth0
在 Auth0 中创建一个应用程序,并获取客户端 ID 和域名。将这些信息添加到 .env.local 文件中:
NEXT_PUBLIC_AUTH0_DOMAIN=
NEXT_PUBLIC_AUTH0_CLIENT_ID=
集成 Auth0 到 React
安装 Auth0 React SDK:
npm install @auth0/auth0-react
在 src/components/authentication 目录下创建 Auth0ProviderWithNavigate.tsx:
import { Auth0Provider } from '@auth0/auth0-react';
const Auth0ProviderWithNavigate = ({ children }) => {
const domain = process.env.NEXT_PUBLIC_AUTH0_DOMAIN;
const clientId = process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID; return (
{children}
);
};export default Auth0ProviderWithNavigate;
总结
通过本教程,您学会了如何使用 Spring Boot 构建 GraphQL API,并通过 React 客户端消费该 API。此外,您还了解了如何使用 Auth0 为应用程序添加身份验证功能。GraphQL 的灵活性使得客户端可以快速调整查询以满足需求,而 Auth0 提供了安全且便捷的身份验证解决方案。
原文链接: https://auth0.com/blog/how-to-build-a-graphql-api-with-spring-boot/