iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
Modern Web

由前向後,從前端邁向全端系列 第 28

28.【從前端到全端,Nextjs+Nestjs】將Nextjs的GraphQL資料改成使用後端資料

  • 分享至 

  • xImage
  •  

文章重點

  • 透過修改前端設置,實現從後端GraphQL伺服器獲取資料
  • 為前端和後端設定了CORS配置

本文

我們今天將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:servepnpm exec nx run iron-ecommerce-next:serve指令,啟動了伺服器和客戶端,然後造訪http://localhost:4200/products,觀看結果
https://ithelp.ithome.com.tw/upload/images/20231014/20108931mXX4xeztIM.png


總結

本文介紹了如何將前端的資料來源從Nest的假資料切換為後端的GraphQL伺服器。並USE_GRAPHQL_MOCK環境變量,在不同的環境中切換資料來源。並且透過設置來解決CORS問題


上一篇
27.【從前端到全端,Nextjs+Nestjs】加入authenication (二)
下一篇
29.【從前端到全端,Nextjs+Nestjs】改進和解決GraphQL問題
系列文
由前向後,從前端邁向全端30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言