网站备案找回,继续网站建设,如何制作网页小游戏,苏州 中英文网站建设技术栈
PostgreSQL hasura Apollo GraphQL React Antd
适用于复杂的查询,快速开发
环境安装
安装PostgreSQL hasura,使用docker安装
使用 Docker Compose 部署时#xff0c;它会同时启动两个容器PostgreSQL和 Hasura GraphQL ,如下
version: 3.6
serv…技术栈
PostgreSQL hasura Apollo GraphQL React Antd
适用于复杂的查询,快速开发
环境安装
安装PostgreSQL hasura,使用docker安装
使用 Docker Compose 部署时它会同时启动两个容器PostgreSQL和 Hasura GraphQL ,如下
version: 3.6
services:postgres:image: postgres:latestcontainer_name: postgresrestart: alwaysvolumes:- ~/data/postgres:/var/lib/postgresql/dataports:- 5432:5432environment:POSTGRES_PASSWORD: postgrespasswordgraphql-engine:image: hasura/graphql-engine:latestcontainer_name: hasuraports:- 23333:8080depends_on:- postgresrestart: alwaysenvironment:HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespasswordpostgres:5432/postgresHASURA_GRAPHQL_ENABLE_CONSOLE: true # set to false to disable consoleHASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log## uncomment next line to set an admin secret# HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey创建一个新文件夹,创建文件docker-compose.yaml复制上面内容,然后运行下面指令以安装
docker-compose up -d安装完成后使用下面指令查看正在运行的docker
docker ps -a在浏览器中输入localhost:23333/console可以进入Hasura的控制界面,根据上面的配置,会自动连接上数据库
PostgreSQL
数据层次
Cluster (集群) 集群是 PostgreSQL 实例的最高级别概念。一个集群包含多个数据库并且所有这些数据库共享同一组配置文件、后台进程和存储区域。集群由一个特定版本的 PostgreSQL 服务器管理。 Database (数据库) 每个集群可以包含多个独立的数据库。每个数据库都是一个逻辑单元拥有自己的模式schema、表、索引等对象。用户连接到特定的数据库进行操作不同数据库中的对象默认情况下是隔离的。 Schema (模式) 模式是数据库内的命名空间用于组织数据库对象如表、视图、函数等。每个数据库至少有一个名为 public 的默认模式但你可以创建额外的模式来更好地组织你的数据和代码。模式有助于避免名称冲突并允许你对数据库对象进行逻辑分组。 Table (表) 表是存储实际数据的地方。每个表都有一个唯一的名称在同一模式内并且由一组列定义每列有其类型和约束。表可以包含零条或多条记录行。
创建实例数据库
CREATE SCHEMA test;
CREATE TABLE test.users (id SERIAL PRIMARY KEY,name VARCHAR(100) NOT NULL,email VARCHAR(150) UNIQUE NOT NULL
);
CREATE TABLE test.orders (id SERIAL PRIMARY KEY,user_id INT REFERENCES test.users(id) ON DELETE CASCADE,product VARCHAR(100) NOT NULL,quantity INT NOT NULL,order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);-- 插入用户
INSERT INTO test.users (name, email)
VALUES (Alice, aliceexample.com),(Bob, bobexample.com);-- 插入订单
INSERT INTO test.orders (user_id, product, quantity)
VALUES (1, Laptop, 1),(1, Mouse, 2),(2, Keyboard, 1);hasura
然后hasura会对schema的每个表建立以下的查询方法
分别是批量查询,聚合查询以及单体查询
test_users
test_users_aggragate
test_users_by_pk 然后我们可以通过点击需要的数据,生成对应的graphQL查询语句,如下,然后在前端使用
query MyQuery {test_users {emailnameid}
}react
创建项目
创建新项目
npx create-react-app user-orders-app
cd user-orders-app启动项目
npm startappollo
安装依赖
npm install apollo/client graphql配置Hasura GraphQL服务器
// src/apollo-client.js
import { ApolloClient, InMemoryCache, HttpLink } from apollo/client;const client new ApolloClient({link: new HttpLink({uri: http://localhost:23333/v1/graphql, // 你的 Hasura GraphQL 端点}),cache: new InMemoryCache(),
});export default client;GraphiQL
编写graphql以直接操作数据库
// src/graphql.js
import { gql } from apollo/client;// 获取所有用户
export const GET_USERS gqlquery GetUsers {test_users {idnameemail}}
;// 获取指定用户的订单
export const GET_USER_ORDERS gqlquery GetUserOrders($userId: Int!) {test_orders(where: { user_id: { _eq: $userId } }) {idproductquantityorder_date}}
;// 创建用户
export const CREATE_USER gqlmutation CreateUser($name: String!, $email: String!) {insert_test_users(objects: { name: $name, email: $email }) {returning {idnameemail}}}
;// 删除用户
export const DELETE_USER gqlmutation DeleteUser($id: Int!) {delete_test_users(where: { id: { _eq: $id } }) {returning {id}}}
;// 更新用户
export const UPDATE_USER gqlmutation UpdateUser($id: Int!, $name: String, $email: String) {update_test_users(where: { id: { _eq: $id } }, _set: { name: $name, email: $email }) {returning {idnameemail}}}
;react
编写react前端页面
// src/UserOrders.js
import React, { useState } from react;
import { useQuery, useMutation } from apollo/client;
import { GET_USERS, GET_USER_ORDERS, CREATE_USER, DELETE_USER, UPDATE_USER } from ./graphql;const UserOrders () {const [newName, setNewName] useState();const [newEmail, setNewEmail] useState();const [updateName, setUpdateName] useState();const [updateEmail, setUpdateEmail] useState();const [selectedUserId, setSelectedUserId] useState(null);// 获取用户列表const { loading, error, data } useQuery(GET_USERS);// 获取指定用户的订单const { loading: ordersLoading, data: ordersData } useQuery(GET_USER_ORDERS, {skip: !selectedUserId,variables: { userId: selectedUserId },});// 调试信息查看获取的数据console.log(User Data:, data);console.log(Orders Data:, ordersData);// 创建用户const [createUser] useMutation(CREATE_USER, {refetchQueries: [{ query: GET_USERS }],});// 删除用户const [deleteUser] useMutation(DELETE_USER, {refetchQueries: [{ query: GET_USERS }],});// 更新用户const [updateUser] useMutation(UPDATE_USER, {refetchQueries: [{ query: GET_USERS }],});const handleCreateUser () {createUser({ variables: { name: newName, email: newEmail } });setNewName();setNewEmail();};const handleDeleteUser (id) {deleteUser({ variables: { id } });};const handleUpdateUser (id) {updateUser({variables: { id, name: updateName, email: updateEmail },});setUpdateName();setUpdateEmail();};return (divh2Create User/h2inputtypetextvalue{newName}onChange{(e) setNewName(e.target.value)}placeholderName/inputtypeemailvalue{newEmail}onChange{(e) setNewEmail(e.target.value)}placeholderEmail/button onClick{handleCreateUser}Create/buttonh2Users/h2{loading pLoading users.../p}{error pError: {error.message}/p}{data (ul{data.test_users.map((user) (li key{user.id}{user.name} ({user.email})button onClick{() setSelectedUserId(user.id)}View Orders/buttonbutton onClick{() handleDeleteUser(user.id)}Delete/buttonbuttononClick{() {setUpdateName(user.name);setUpdateEmail(user.email);handleUpdateUser(user.id);}}Update/button/li))}/ul)}{selectedUserId ordersData (divh3Orders for {data.test_users.find((user) user.id selectedUserId).name}/h3{ordersLoading ? (pLoading orders.../p) : (ul{ordersData.test_orders ordersData.test_orders.length 0 ? (ordersData.test_orders.map((order) (li key{order.id}{order.product} - {order.quantity} (Ordered on {new Date(order.order_date).toLocaleString()})/li))) : (pNo orders found for this user./p)}/ul)}/div)}/div);
};export default UserOrders;然后再App.js中使用
// src/App.js
import React from react;
import { ApolloProvider } from apollo/client;
import client from ./apollo-client;
import UserOrders from ./UserOrders;function App() {return (ApolloProvider client{client}div classNameApph1Users and Orders/h1UserOrders //div/ApolloProvider);
}export default App;antd
ant design 蚂蚁组件库,爱来自阿里,组件库,用于美化前端页面
安装
npm install antd^4.24.2
npm install ant-design/icons先在index.js中引入
import antd/dist/antd.css;然后对react页面应用样式
// src/UserList.js
import React, { useEffect, useState } from react;
import { Table, Button, Space, Modal, Form, Input, message } from antd;
import { useQuery, useMutation } from apollo/client;
import { GET_USERS, DELETE_USER, CREATE_USER, UPDATE_USER, GET_USER_ORDERS } from ./graphql;// 用户列表组件
const UserList () {const { loading, error, data, refetch } useQuery(GET_USERS);const [deleteUser] useMutation(DELETE_USER);const [createUser] useMutation(CREATE_USER);const [updateUser] useMutation(UPDATE_USER);const [isModalVisible, setIsModalVisible] useState(false);const [isOrdersModalVisible, setIsOrdersModalVisible] useState(false);const [form] Form.useForm();const [editingUser, setEditingUser] useState(null);const [selectedUser, setSelectedUser] useState(null);const [orders, setOrders] useState([]);const { data: ordersData, loading: ordersLoading, error: ordersError } useQuery(GET_USER_ORDERS, {variables: { userId: selectedUser?.id },skip: !selectedUser, // 如果没有选择用户则跳过该查询onCompleted: (data) setOrders(data?.test_orders || []),});// 显示删除用户的确认对话框const handleDelete async (userId) {try {await deleteUser({ variables: { id: userId } });message.success(User deleted successfully);refetch(); // 刷新列表} catch (err) {message.error(Failed to delete user);}};// 显示/隐藏模态框const showModal (user) {setEditingUser(user);form.setFieldsValue(user || { name: , email: });setIsModalVisible(true);};const handleOk async () {try {const values await form.validateFields();if (editingUser) {// 更新用户await updateUser({variables: { id: editingUser.id, name: values.name, email: values.email },});message.success(User updated successfully);} else {// 创建新用户await createUser({variables: { name: values.name, email: values.email },});message.success(User created successfully);}setIsModalVisible(false);refetch(); // 刷新列表} catch (err) {message.error(Failed to save user);}};const handleCancel () {setIsModalVisible(false);setIsOrdersModalVisible(false);};const handleUserClick (user) {setSelectedUser(user);setIsOrdersModalVisible(true);};const handleOrdersModalClose () {setSelectedUser(null);setIsOrdersModalVisible(false);};const columns [{title: Name,dataIndex: name,key: name,},{title: Email,dataIndex: email,key: email,},{title: Actions,key: actions,render: (text, record) (Space sizemiddleButton typelink onClick{() showModal(record)}Edit/ButtonButton typelink danger onClick{() handleDelete(record.id)}Delete/ButtonButton typelink onClick{() handleUserClick(record)}View Orders/Button/Space),},];const orderColumns [{title: Product,dataIndex: product,key: product,},{title: Quantity,dataIndex: quantity,key: quantity,},{title: Order Date,dataIndex: order_date,key: order_date,render: (date) new Date(date).toLocaleString(),},];if (loading) return divLoading.../div;if (error) return divError loading users/div;return (divButton typeprimary onClick{() showModal(null)} style{{ marginBottom: 16 }}Add User/ButtonTablecolumns{columns}dataSource{data.test_users}rowKeyid/Modaltitle{editingUser ? Edit User : Create User}visible{isModalVisible}onOk{handleOk}onCancel{handleCancel}confirmLoading{loading}Formform{form}layoutverticalnameuserFormForm.ItemlabelNamenamenamerules{[{ required: true, message: Please input the name! }]}Input //Form.ItemForm.ItemlabelEmailnameemailrules{[{ required: true, message: Please input the email! }, { type: email, message: Please input a valid email! }]}Input //Form.Item/Form/ModalModaltitle{${selectedUser?.name}s Orders}visible{isOrdersModalVisible}onCancel{handleOrdersModalClose}footer{null}{ordersLoading ? (divLoading orders.../div) : ordersError ? (divError loading orders/div) : (Tablecolumns{orderColumns}dataSource{orders}rowKeyid/)}/Modal/div);
};export default UserList;