PostgreSQL + hasura + Apollo + GraphQL + React + Antd
技术栈
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:5432"environment:POSTGRES_PASSWORD: postgrespasswordgraphql-engine:image: hasura/graphql-engine:latestcontainer_name: hasuraports:- "23333:8080"depends_on:- "postgres"restart: alwaysenvironment:HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres: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', 'alice@example.com'),('Bob', 'bob@example.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 start
appollo
安装依赖
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 = gql`query GetUsers {test_users {idnameemail}}
`;// 获取指定用户的订单
export const GET_USER_ORDERS = gql`query GetUserOrders($userId: Int!) {test_orders(where: { user_id: { _eq: $userId } }) {idproductquantityorder_date}}
`;// 创建用户
export const CREATE_USER = gql`mutation CreateUser($name: String!, $email: String!) {insert_test_users(objects: { name: $name, email: $email }) {returning {idnameemail}}}
`;// 删除用户
export const DELETE_USER = gql`mutation DeleteUser($id: Int!) {delete_test_users(where: { id: { _eq: $id } }) {returning {id}}}
`;// 更新用户
export const UPDATE_USER = gql`mutation 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 (<div><h2>Create User</h2><inputtype="text"value={newName}onChange={(e) => setNewName(e.target.value)}placeholder="Name"/><inputtype="email"value={newEmail}onChange={(e) => setNewEmail(e.target.value)}placeholder="Email"/><button onClick={handleCreateUser}>Create</button><h2>Users</h2>{loading && <p>Loading users...</p>}{error && <p>Error: {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</button><button onClick={() => handleDeleteUser(user.id)}>Delete</button><buttononClick={() => {setUpdateName(user.name);setUpdateEmail(user.email);handleUpdateUser(user.id);}}>Update</button></li>))}</ul>)}{selectedUserId && ordersData && (<div><h3>Orders for {data.test_users.find((user) => user.id === selectedUserId).name}</h3>{ordersLoading ? (<p>Loading 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>))) : (<p>No 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 className="App"><h1>Users and Orders</h1><UserOrders /></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 size="middle"><Button type="link" onClick={() => showModal(record)}>Edit</Button><Button type="link" danger onClick={() => handleDelete(record.id)}>Delete</Button><Button type="link" 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 <div>Loading...</div>;if (error) return <div>Error loading users</div>;return (<div><Button type="primary" onClick={() => showModal(null)} style={{ marginBottom: 16 }}>Add User</Button><Tablecolumns={columns}dataSource={data.test_users}rowKey="id"/><Modaltitle={editingUser ? 'Edit User' : 'Create User'}visible={isModalVisible}onOk={handleOk}onCancel={handleCancel}confirmLoading={loading}><Formform={form}layout="vertical"name="userForm"><Form.Itemlabel="Name"name="name"rules={[{ required: true, message: 'Please input the name!' }]}><Input /></Form.Item><Form.Itemlabel="Email"name="email"rules={[{ required: true, message: 'Please input the email!' }, { type: 'email', message: 'Please input a valid email!' }]}><Input /></Form.Item></Form></Modal><Modaltitle={`${selectedUser?.name}'s Orders`}visible={isOrdersModalVisible}onCancel={handleOrdersModalClose}footer={null}>{ordersLoading ? (<div>Loading orders...</div>) : ordersError ? (<div>Error loading orders</div>) : (<Tablecolumns={orderColumns}dataSource={orders}rowKey="id"/>)}</Modal></div>);
};export default UserList;
相关文章:
PostgreSQL + hasura + Apollo + GraphQL + React + Antd
技术栈 PostgreSQL hasura Apollo GraphQL React Antd 适用于复杂的查询,快速开发 环境安装 安装PostgreSQL hasura,使用docker安装 使用 Docker Compose 部署时,它会同时启动两个容器PostgreSQL和 Hasura GraphQL ,如下 version: "3.6" serv…...
Android笔记【10】
一、前言 学习课程时,对于自己不懂的点的记录。 二、内容 学习一段代码: val drawerState rememberDrawerState(DrawerValue.Closed)val scope rememberCoroutineScope()Scaffold (topBar{TopAppBar(navigationIcon {IconButton(onClick {scope.lau…...
Leetcode打卡:N皇后
执行结果:通过 题目:51 N皇后 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上,并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#…...
Linux内核4.14版本——ccf时钟子系统(3)——ccf一些核心结构体
目录 1. struct clk_hw 2. struct clk_ops 3. struct clk_core 4. struct clk_notifier 5. struct clk 6. struct clk_gate 7. struct clk_divider 8. struct clk_mux 9. struct clk_fixed_factor 10. struct clk_fractional_divider 11. struct clk_multiplier 12…...
[Deep Learning] 深度学习中常用函数的整理与介绍(pytorch为例)
文章目录 深度学习中常用函数的整理与介绍常见损失函数1. L2_loss | nn.MSELoss()公式表示:特点:应用:缺点:主要参数:示例用法:注意事项: 2. L1 Loss | nn.L1Loss数学定义:特点&…...
【ETCD】etcd简单入门之单节点部署etcd
etcd 是一个分布式可靠的键值存储系统,用于分布式系统中最关键的数据,主要特点包括: 简单:具有明确的、面向用户的 API(gRPC) 安全:自动 TLS 支持,并可选的客户端证书认证 快速&am…...
Cadence基础语法
03-Cadence基础语法 0 Cadence基础语法入门:流程编排语言的新星 Cadence是由Uber开发的一种领域特定语言(Domain-Specific Language,DSL),专门用于编写可扩展的长时间运行的业务流程。它是Temporal工作流引擎的核心组…...
GAMES101虚拟机使用教程与探讨
写在前面 环境配置请参考作业0的pdf,本文章主要对于配置好环境后怎么使用以及遇到的问题进行探讨(要是有更方便的使用方式欢迎在评论区讨论),自己刚开始用的时候也折腾了好久,希望能为后来学习的小伙伴节约一点工具使…...
王道考研编程题总结
我还在完善中,边复习边完善(这个只是根据我自身总结的) 一、 线性表 1. 结构体 #define MaxSize 40 typedef struct{ElemType data[MaxSize];int length; }SqList 2. 编程题 1. 删除最小值 题意 :从顺序表中删除…...
算法2--滑动窗口
滑动窗口 滑动窗口经典例题长度最小的子数组无重复字符的最长子串[最大连续1的个数 III](https://leetcode.cn/problems/max-consecutive-ones-iii/description/)[将 x 减到 0 的最小操作数](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/description…...
pycharm或conda中配置镜像源
文章目录 1. 为什么要配置镜像源2. pycharm配置2.1使用pip配置国内镜像源2.2 Pycharm中更改镜像源 3.conda配置镜像源3.1 使用conda命令3.2 文件所在位置(进行增删)3.3 conda常用的几个命令 参考文献 1. 为什么要配置镜像源 由于Python在下载包时&#…...
C#基础之方法
文章目录 1 方法1.1 定义方法1.2 参数传递1.2.1 按值传递参数1.2.2 按引用传递参数1.2.3 按输出传递参数1.2.4 可变参数 params1.2.5 具名参数1.2.6 可选参数 1.3 匿名方法1.3.1 Lambda 表达式1.3.1.1 定义1.3.1.2 常用类型1.3.1.3 Lambda 表达式与 LINQ1.3.1.4 Lambda 表达式的…...
JVM 性能调优 -- JVM常用调优工具【jps、jstack、jmap、jstats 命令】
前言: 前面我们分析怎么去预估系统资源,怎么去设置 JVM 参数以及怎么去看 GC 日志,本篇我们分享一些常用的 JVM 调优工具,我们在进行 JVM 调优的时候,通常需要借助一些工具来对系统的进行相关分析,从而确定…...
PostgreSQL 三种关库模式
PostgreSQL 三种关库模式 基础信息 OS版本:Red Hat Enterprise Linux Server release 7.9 (Maipo) DB版本:16.2 pg软件目录:/home/pg16/soft pg数据目录:/home/pg16/data 端口:5777PostgreSQL 提供了三种关库模式&…...
《运放秘籍》第二部:仪表放大器专项知识点总结
一、差分放大器与仪表放大器的讨论 1.1. 仪放的前世今生——差分放大器原理? 1.2. 差分放大的原理 1.3. 差分放大器检测电流 1.4. 差分放大器端一:输入阻抗 1.5. 差分放大器端二:共模抑制比 1.6. 为什么关注输入阻抗?共模抑…...
C++STL之vector(超详细)
CSTL之vector 1.vector基本介绍2.vector重要接口2.1.构造函数2.2.迭代器2.3.空间2.3.1.resize2.3.2.capacity 2.4.增删查找 3.迭代器失效4.迭代器分类 🌟🌟hello,各位读者大大们你们好呀🌟🌟 🚀Ὠ…...
ubuntu环境下安装electron环境,并快速打包
1.配置镜像源 关闭防火墙,命令:sudo ufw disable 1.1配置国内镜像源: vim /etc/apt/source.list deb https://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiversedeb-src https://mirrors.aliyun.com/ubuntu/ jammy main…...
【Pytorch】优化器(Optimizer)模块‘torch.optim’
torch.optim 是 PyTorch 中提供的优化器(Optimizer)模块,用于优化神经网络模型的参数,更新网络权重,使得模型在训练过程中最小化损失函数。它提供了多种常见的优化算法,如 梯度下降法(SGD&#…...
API平台建设之路:从0到1的实践指南
在这个互联网蓬勃发展的时代,API已经成为连接各个系统、服务和应用的重要纽带。搭建一个优质的API平台不仅能为开发者提供便利,更能创造可观的商业价值。让我们一起探讨如何打造一个成功的API平台。 技术架构是API平台的根基。选择合适的技术栈对平台的…...
【Flink-scala】DataStream编程模型之窗口计算-触发器-驱逐器
DataStream API编程模型 1.【Flink-Scala】DataStream编程模型之数据源、数据转换、数据输出 2.【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序 文章目录 DataStream API编程模型前言1.触发器1.1 代码示例 2.驱逐器2.1 代码示例 总结 前言 本小节我想…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
