我們今天將Nest使用的假資料改成使用我們後端的GraphQL。
首先,我們打開環境變數env加入變數決定是使用mocked還是來自後端。加入USE_GRAPHQL_MOCK
///// .env
# POSTGRES
POSTGRES_USER=dbuser
POSTGRES_PASSWORD=dbpass
POSTGRES_DB=iron-ecommerce
# Nest run locally
DB_HOST=localhost
# Nest run in docker, change host to database container name
# DB_HOST=postgres
DB_PORT=5432
DB_SCHEMA=iron-ecommerce-test
# Prisma database connection
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:${DB_PORT}/${POSTGRES_DB}?schema=${DB_SCHEMA}&sslmode=prefer
JWT_ACCESS_SECRET=nestjsPrismaAccessSecret
JWT_REFRESH_SECRET=nestjsPrismaRefreshSecret
USE_GRAPHQL_MOCK = false
接著來修改我們的client,首先更改port來連接到後端GraphQL server。並且修改一下條件判斷:
///// apps\iron-ecommerce-next\libs\graphql\apollo-client.ts
import { mockedCartItems, mockedProducts } from "./graphql-mocks";
import schema from "./schema.graphql";
import { ApolloLink, HttpLink } from "@apollo/client";
import {
NextSSRApolloClient,
NextSSRInMemoryCache,
SSRMultipartLink
} from "@apollo/experimental-nextjs-app-support/ssr";
// ....
export const makeGraphqlClient = () => {
const httpLink = new HttpLink({
uri: "http://localhost:3000/graphql"
});
return new NextSSRApolloClient({
cache:
process.env.NODE_ENV === "development" && process.env.USE_GRAPHQL_MOCK === "true"
? mockedCache
: new NextSSRInMemoryCache(),
link:
typeof window === "undefined"
? ApolloLink.from([
new SSRMultipartLink({
stripDefer: true
}),
httpLink
])
: httpLink,
typeDefs: schema
});
};
接著修正我們的server,加入enableCors。
///// apps\iron-ecommerce-server\src\main.ts
import { AppModule } from "./app/app.module";
import { Logger } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const globalPrefix = "api";
app.setGlobalPrefix(globalPrefix);
app.enableCors({
origin: true,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS",
credentials: true
});
const port = process.env.PORT || 3000;
await app.listen(port);
Logger.log(`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`);
}
bootstrap();
在我們的next.config.js加入proxy,使用next設置而不是使用apps\iron-ecommerce-next\proxy.conf.json
來設置proxy的原因是因為目前使用Nx版本的proxy會不work,不確定原因為何?
///// apps\iron-ecommerce-next\next.config.js
//@ts-check
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { composePlugins, withNx } = require("@nx/next");
/**
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
**/
const nextConfig = {
nx: {
// Set this to true if you would like to use SVGR
// See: https://github.com/gregberge/svgr
svgr: false
},
webpack: (config) => {
config.module.rules.push({
test: /\.graphql$/,
exclude: /node_modules/,
use: ["graphql-tag/loader"]
});
return config;
},
async rewrites() {
return [
{
source: "/graphql",
destination: "http://localhost:3000/graphql"
}
];
}
};
const plugins = [
// Add more Next.js plugins to this list if needed.
withNx
];
module.exports = composePlugins(...plugins)(nextConfig);
接著更新我們的schema,將新的schema放入到apps\iron-ecommerce-next\libs\graphql\schema.graphql
scalar DateTime
scalar JWT
input NewProductInput {
name: String!
price: Float!
description: String!
imageUrl: String!
}
input UpdateProductInput {
id: ID!
name: String!
price: Float!
description: String!
imageUrl: String!
}
type Product {
id: ID!
name: String!
price: Float!
description: String!
imageUrl: String!
}
type ProductEdge {
cursor: String!
node: Product!
}
type ProductConnection {
edges: [ProductEdge!]!
nodes: [Product!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PageInfo {
endCursor: String
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
}
type CartItem {
productId: ID!
productName: String!
price: Float!
quantity: Int!
}
input CartItemInput {
productId: ID!
quantity: Int!
}
type Order {
id: ID!
items: [CartItem!]!
orderDate: DateTime!
}
type Auth {
accessToken: JWT!
refreshToken: JWT!
user: User!
}
input LoginInput {
email: String!
password: String!
}
input SignupInput {
email: String!
password: String!
username: String!
}
input RefreshTokenInput {
token: JWT!
}
input UpdateUserDataInput {
username: String
}
enum Role {
ADMIN
USER
}
type User {
createdAt: DateTime!
email: String!
username: String!
id: ID!
role: Role!
updatedAt: DateTime!
}
type Token {
accessToken: JWT!
refreshToken: JWT!
}
type Query {
me(userId: ID!): User!
getProducts(first: Int, after: String, last: Int, before: String): ProductConnection!
getProduct(id: ID!): Product
getUserProfile: User
getCartItems: [CartItem!]!
}
type Mutation {
login(data: LoginInput!): Auth!
signup(data: SignupInput!): Auth!
refreshToken(token: RefreshTokenInput!): Token!
updateUser(userId: ID!, newUserData: UpdateUserDataInput!): User!
addProduct(input: NewProductInput!): Product
updateProduct(input: UpdateProductInput!): Product
deleteProduct(id: ID!): Boolean
addCartItem(productId: ID!, quantity: Int!): [CartItem!]!
removeCartItem(productId: ID!): [CartItem!]!
updateCartItem(productId: ID!, quantity: Int!): [CartItem!]!
checkout(cartItems: [CartItemInput!]!): Order
}
執行pnpm exec nx run iron-ecommerce-next:graphql-generate
打開我們的page並修改更新了頁面的程式碼,以適應新的GraphQL查詢結構:
///// apps\iron-ecommerce-next\app\products\products.client.tsx
"use client";
import { gql, useQuery } from "@apollo/client";
import { Flex } from "@radix-ui/themes";
import { Get_ProductsQuery } from "../../__generated__/generated-hooks";
import { useCartActions } from "../../store/actions/cart.actions";
import { Product } from "../../store/schemas/product.schema";
import ProductCard from "libs/iron-components/src/lib/ProductCard";
interface ProductsProps {
products: Product[];
}
const ProductsClient = ({ products }: ProductsProps) => {
const { addToCart } = useCartActions();
const { data } = useQuery<Get_ProductsQuery>(gql`
query GET_PRODUCTS {
getProducts {
nodes {
id
name
description
price
imageUrl
}
}
}
`);
const productsData = data?.getProducts.nodes ?? [];
return (
<Flex align="center" justify="center">
<section className="w:60% p:1rem flex flex:wrap flex-direction:row gap:1rem jc:center">
{productsData.map((product, i) => (
<ProductCard
key={product.id}
title={product.name}
price={`$${product.price.toFixed(2)}`}
description={product.description}
width="30%"
imageUrl={product.imageUrl}
onAddToCart={() =>
addToCart({
productId: product.id,
productName: product.name,
quantity: 1,
price: product.price
})
}
/>
))}
</section>
</Flex>
);
};
export default ProductsClient;
透過執行pnpm exec nx run iron-ecommerce-server:serve
和pnpm exec nx run iron-ecommerce-next:serve
指令,啟動了伺服器和客戶端,然後造訪http://localhost:4200/products
,觀看結果
本文介紹了如何將前端的資料來源從Nest的假資料切換為後端的GraphQL伺服器。並USE_GRAPHQL_MOCK環境變量,在不同的環境中切換資料來源。並且透過設置來解決CORS問題