├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── server ├── api │ ├── User.js │ └── index.js ├── config.js ├── graphql │ └── index.js ├── model │ └── user.js └── server.js └── src ├── App.js ├── graphql └── user.js ├── index.js ├── scss ├── index.css └── index.scss └── service └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /.vscode 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .env 17 | .json 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GRAPHQL CRUD 2 | 3 | You can learn about graphql basic knowledge in here. 4 | 5 | This project is thing for beginners... -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "walletconnec", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.5.10", 7 | "@testing-library/jest-dom": "^5.16.2", 8 | "@testing-library/react": "^12.1.2", 9 | "@testing-library/user-event": "^13.5.0", 10 | "apollo-server": "^3.6.4", 11 | "apollo-server-express": "^3.6.4", 12 | "bootstrap": "^5.1.3", 13 | "formik": "^2.2.9", 14 | "graphql": "^16.3.0", 15 | "react": "^17.0.2", 16 | "react-datepicker": "^4.7.0", 17 | "react-dom": "^17.0.2", 18 | "react-scripts": "5.0.0", 19 | "react-time-format": "0.0.5", 20 | "react-toastify": "^8.2.0", 21 | "sass": "^1.49.7", 22 | "web-vitals": "^2.1.4", 23 | "yup": "^0.32.11" 24 | }, 25 | "scripts": { 26 | "server": "nodemon ./server/server", 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "bluebird": "^3.7.2", 52 | "cors": "^2.8.5", 53 | "dotenv": "^16.0.0", 54 | "express": "^4.17.3", 55 | "mongoose": "^6.2.6", 56 | "nodemon": "^2.0.15" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigBen3918/graphql_crud/6818a3cdc67f34384bc5b5bab3d10ea69aedf16d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigBen3918/graphql_crud/6818a3cdc67f34384bc5b5bab3d10ea69aedf16d/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigBen3918/graphql_crud/6818a3cdc67f34384bc5b5bab3d10ea69aedf16d/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/api/User.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | add: async (req, res) => { 3 | const { param } = req.body; 4 | console.log(param); 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /server/api/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const User = require("./User"); 4 | 5 | module.exports = (router) => { 6 | router.post("/add-user", User.add); 7 | }; 8 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | const { gql } = require("apollo-server-express"); 2 | 3 | const typeDefs = gql` 4 | scalar Date 5 | 6 | type User { 7 | id: ID 8 | name: String 9 | gender: Int 10 | birthday: Date 11 | } 12 | 13 | type Query { 14 | getUsers: [User] 15 | findUser(name: String): User 16 | } 17 | 18 | type Mutation { 19 | addUser(name: String, gender: Int, birthday: Date): User 20 | updateUser(id: ID, name: String, gender: Int, birthday: Date): User 21 | deleteUser(id: ID): User 22 | } 23 | `; 24 | 25 | module.exports = { typeDefs }; 26 | -------------------------------------------------------------------------------- /server/graphql/index.js: -------------------------------------------------------------------------------- 1 | const UserModel = require("../model/user"); 2 | 3 | const resolvers = { 4 | Query: { 5 | getUsers: async (parent, args, context, info) => { 6 | return UserModel.find() 7 | .then((result) => { 8 | return result; 9 | }) 10 | .catch((err) => { 11 | console.log(err); 12 | }); 13 | }, 14 | findUser: async (parent, args, context, info) => { 15 | const { id } = args; 16 | return UserModel.findOne({ 17 | _id: id, 18 | }) 19 | .then((result) => { 20 | return result; 21 | }) 22 | .catch((err) => { 23 | console.log(err); 24 | }); 25 | }, 26 | }, 27 | Mutation: { 28 | addUser: async (parent, args, context, info) => { 29 | const { name, gender, birthday } = args; 30 | 31 | const checkUser = await UserModel.findOne({ name: name }); 32 | 33 | if (checkUser) { 34 | return; 35 | } 36 | 37 | const newUser = new UserModel({ 38 | name: name, 39 | gender: gender, 40 | birthday: birthday, 41 | }); 42 | return newUser 43 | .save() 44 | .then((result) => { 45 | return result; 46 | }) 47 | .catch((err) => { 48 | console.log(err); 49 | }); 50 | }, 51 | updateUser: async (parent, args, context, info) => { 52 | const { id, name, gender, birthday } = args; 53 | 54 | let flag = false; 55 | const checkUser = await UserModel.find({ name: name }); 56 | checkUser.map((user) => { 57 | if (user._id.toString() !== id) { 58 | flag = true; 59 | return; 60 | } 61 | }); 62 | 63 | if (flag) return; 64 | 65 | return UserModel.updateOne( 66 | { 67 | _id: id, 68 | }, 69 | { 70 | $set: { 71 | name: name, 72 | gender: gender, 73 | birthday: birthday, 74 | }, 75 | } 76 | ) 77 | .then((result) => { 78 | return result; 79 | }) 80 | .catch((err) => { 81 | console.log(err); 82 | }); 83 | }, 84 | deleteUser: async (parent, args, context, info) => { 85 | const { id } = args; 86 | return UserModel.deleteOne({ 87 | _id: id, 88 | }) 89 | .then((result) => { 90 | return result; 91 | }) 92 | .catch((err) => { 93 | console.log(err); 94 | }); 95 | }, 96 | }, 97 | }; 98 | 99 | module.exports = { resolvers }; 100 | -------------------------------------------------------------------------------- /server/model/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const UserSchema = Schema({ 5 | name: { 6 | type: String, 7 | }, 8 | gender: { 9 | type: Number, 10 | }, 11 | birthday: { 12 | type: Date, 13 | }, 14 | }); 15 | 16 | module.exports = User = mongoose.model("users", UserSchema); 17 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { ApolloServer } = require("apollo-server-express"); 3 | const mongoose = require("mongoose"); 4 | const router = express.Router(); 5 | const routes = require("./api"); 6 | const { typeDefs } = require("./config"); 7 | const { resolvers } = require("./graphql"); 8 | require("dotenv").config(); 9 | const cors = require("cors"); 10 | 11 | const app = express(); 12 | 13 | app.use(express.json()); 14 | app.use( 15 | cors({ 16 | origin: "*", 17 | methods: ["POST", "GET"], 18 | }) 19 | ); 20 | 21 | mongoose 22 | .connect("mongodb://localhost/graphqlDB", { 23 | promiseLibrary: require("bluebird"), 24 | useNewUrlParser: true, 25 | }) 26 | .then(() => console.log("Successfully Connected MongoDB")) 27 | .catch((err) => console.error(err)); 28 | 29 | routes(router); 30 | app.use("/api", router); 31 | 32 | // app.use(express.static(__dirname + "/build")); 33 | // app.get("/*", function (req, res) { 34 | // res.sendFile(__dirname + "/build/index.html", function (err) { 35 | // if (err) { 36 | // res.status(500).send(err); 37 | // } 38 | // }); 39 | // }); 40 | 41 | const startApolloServer = async (typeDefs, resolvers) => { 42 | const server = new ApolloServer({ typeDefs, resolvers }); 43 | 44 | await server.start(); 45 | server.applyMiddleware({ app, path: "/graphql" }); 46 | 47 | const port = process.env.SERVER_PORT || 5555; 48 | app.listen(port, () => console.log(`Running on port ${port}`)); 49 | }; 50 | 51 | startApolloServer(typeDefs, resolvers); 52 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from "react"; 2 | import { useFormik } from "formik"; 3 | import * as Yup from "yup"; 4 | import DatePicker from "react-datepicker"; 5 | import { useMutation, useLazyQuery } from "@apollo/client"; 6 | import { toast } from "react-toastify"; 7 | import Time from "react-time-format"; 8 | import { 9 | ADD_USER, 10 | UPDATE_USER, 11 | DELETE_USER, 12 | getAllUsers, 13 | } from "./graphql/user"; 14 | 15 | function App() { 16 | const [loading, setLoading] = useState(false); 17 | const [tableLoading, setTableLoading] = useState(false); 18 | const [addUser] = useMutation(ADD_USER); 19 | const [updateUser] = useMutation(UPDATE_USER); 20 | const [deleteUser] = useMutation(DELETE_USER); 21 | const [getUsers] = useLazyQuery(getAllUsers); 22 | const [userdata, setUserData] = useState([]); 23 | const [preData, setPreData] = useState([]); 24 | const cancelButton = useRef(null); 25 | 26 | useEffect(() => { 27 | getUserData(); 28 | }, []); 29 | 30 | useEffect(() => { 31 | if (preData.length !== 0) { 32 | update_formik.setFieldValue("update_name", preData[0].name); 33 | update_formik.setFieldValue("update_gender", preData[0].gender); 34 | update_formik.setFieldValue( 35 | "update_birthday", 36 | new Date(preData[0].birthday) 37 | ); 38 | } 39 | }, [preData]); 40 | 41 | const formik = useFormik({ 42 | initialValues: { 43 | name: "", 44 | gender: "1", 45 | birthday: "", 46 | }, 47 | validationSchema: Yup.object().shape({ 48 | name: Yup.string() 49 | .min(3, "name is too short.") 50 | .required("fill the name"), 51 | birthday: Yup.string().required("fill the birthday"), 52 | }), 53 | onSubmit: async (values, { resetForm }) => { 54 | setLoading(true); 55 | try { 56 | const result = await addUser({ 57 | variables: { 58 | name: values.name, 59 | gender: Number(values.gender), 60 | birthday: values.birthday, 61 | }, 62 | }); 63 | 64 | setLoading(false); 65 | 66 | if (!result.data.addUser) { 67 | toast.error("Name already exist"); 68 | } else { 69 | toast.success("Add Success"); 70 | getUserData(); 71 | } 72 | 73 | resetForm({ values: "" }); 74 | } catch (err) { 75 | console.log(err.message); 76 | setLoading(false); 77 | toast.error("Failed Add"); 78 | } 79 | }, 80 | }); 81 | 82 | const update_formik = useFormik({ 83 | initialValues: { 84 | update_name: "", 85 | update_gender: "1", 86 | update_birthday: "", 87 | }, 88 | validationSchema: Yup.object().shape({ 89 | update_name: Yup.string() 90 | .min(3, "name is too short.") 91 | .required("fill the name"), 92 | update_birthday: Yup.string().required("fill the birthday"), 93 | }), 94 | onSubmit: async (values, { resetForm }) => { 95 | try { 96 | const result = await updateUser({ 97 | variables: { 98 | id: preData[0].id, 99 | name: values.update_name, 100 | gender: Number(values.update_gender), 101 | birthday: values.update_birthday, 102 | }, 103 | }); 104 | if (!result.data.updateUser) { 105 | toast.error("Name already exist"); 106 | } else { 107 | toast.success("Update Success"); 108 | cancelButton.current.click(); 109 | getUserData(); 110 | resetForm({ values: "" }); 111 | } 112 | } catch (err) { 113 | console.log(err.message); 114 | cancelButton.current.click(); 115 | toast.error("Failed Update"); 116 | } 117 | }, 118 | }); 119 | 120 | const getUserData = async () => { 121 | setTableLoading(true); 122 | try { 123 | const result = await getUsers(); 124 | setUserData(result.data.getUsers); 125 | setTableLoading(false); 126 | } catch (err) { 127 | console.log(err.message); 128 | setTableLoading(false); 129 | } 130 | }; 131 | 132 | const updateModal = async (param) => { 133 | const result = userdata.filter((item) => item.id === param); 134 | setPreData(result); 135 | }; 136 | 137 | const removeUser = async (param) => { 138 | try { 139 | const result = await deleteUser({ 140 | variables: { 141 | id: param, 142 | }, 143 | }); 144 | 145 | if (result.data.deleteUser.name) { 146 | toast.error("Server Error"); 147 | } else { 148 | toast.success("Delete Success"); 149 | getUserData(); 150 | } 151 | } catch (err) { 152 | console.log(err.message); 153 | toast.error("Failed Delete"); 154 | } 155 | }; 156 | 157 | return ( 158 | <> 159 |
160 |
161 |
162 |
163 | 164 | 172 | {formik.touched.name && formik.errors.name ? ( 173 | 174 | {formik.errors.name} 175 | 176 | ) : null} 177 |
178 |
179 | 180 | 189 |
190 |
191 | 192 | { 198 | formik.setFieldValue("birthday", val); 199 | }} 200 | minDate={new Date("01/01/1900")} 201 | maxDate={new Date()} 202 | onBlur={formik.handleBlur} 203 | autoComplete="off" 204 | /> 205 | {formik.touched.birthday && 206 | formik.errors.birthday ? ( 207 | 208 | {formik.errors.birthday} 209 |
210 |
211 |
212 | ) : null} 213 |
214 |
215 |
216 |
217 | {loading ? ( 218 | 221 | ) : ( 222 | 228 | )} 229 |
230 |
231 |
232 |
233 | {tableLoading ? ( 234 |
235 |
236 |
237 | ) : ( 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | {userdata.map((item, index) => { 250 | return ( 251 | 252 | 253 | 254 | 259 | 265 | 288 | 289 | ); 290 | })} 291 | 292 |
NoNameGenderBirthdayAction
{index + 1}{item.name} 255 | {item.gender === 1 256 | ? "Male" 257 | : "Female"} 258 | 260 | 266 | 277 |     278 | 287 |
293 | )} 294 |
295 | 296 | {/* Edit Modal */} 297 | 417 | 418 | ); 419 | } 420 | 421 | export default App; 422 | -------------------------------------------------------------------------------- /src/graphql/user.js: -------------------------------------------------------------------------------- 1 | import { gql } from "@apollo/client"; 2 | 3 | // Query 4 | const getAllUsers = gql` 5 | query GetUsers { 6 | getUsers { 7 | id 8 | name 9 | gender 10 | birthday 11 | } 12 | } 13 | `; 14 | 15 | const findUser = gql` 16 | query FindUser($id: ID) { 17 | findUser(id: $id) { 18 | id 19 | name 20 | gender 21 | birthday 22 | } 23 | } 24 | `; 25 | 26 | // Mutation 27 | const ADD_USER = gql` 28 | mutation AddUser($name: String, $gender: Int, $birthday: Date) { 29 | addUser(name: $name, gender: $gender, birthday: $birthday) { 30 | id 31 | name 32 | gender 33 | birthday 34 | } 35 | } 36 | `; 37 | 38 | const UPDATE_USER = gql` 39 | mutation UpdateUser( 40 | $id: ID! 41 | $name: String 42 | $gender: Int 43 | $birthday: Date 44 | ) { 45 | updateUser(id: $id, name: $name, gender: $gender, birthday: $birthday) { 46 | id 47 | name 48 | gender 49 | birthday 50 | } 51 | } 52 | `; 53 | 54 | const DELETE_USER = gql` 55 | mutation DeleteUser($id: ID!) { 56 | deleteUser(id: $id) { 57 | id 58 | } 59 | } 60 | `; 61 | 62 | export { ADD_USER, UPDATE_USER, DELETE_USER, getAllUsers, findUser }; 63 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { ApolloProvider } from "@apollo/client"; 5 | import { ToastContainer } from "react-toastify"; 6 | import client from "./service"; 7 | 8 | import "bootstrap/dist/js/bootstrap.min"; 9 | 10 | import "bootstrap/dist/css/bootstrap.min.css"; 11 | import "react-datepicker/dist/react-datepicker.css"; 12 | import "react-toastify/dist/ReactToastify.css"; 13 | import "./scss/index.scss"; 14 | 15 | ReactDOM.render( 16 | 17 | 18 | 19 | 20 | 21 | , 22 | document.getElementById("root") 23 | ); 24 | -------------------------------------------------------------------------------- /src/scss/index.css: -------------------------------------------------------------------------------- 1 | body{background-color:#fff}.spacer-single{height:30px}.spacer-double{height:50px}.header{margin-top:20px}.userlist{margin-top:50px}.spinner-border{width:10rem;height:10rem}.table_load{padding-top:20%;text-align:center}.error{color:red}.Toastify__toast--success{color:#07bc0c !important;border:1.2px solid #07bc0c !important;background:transparent !important}.Toastify__toast--error{color:red !important;border:1.2px solid red !important;background:transparent !important} -------------------------------------------------------------------------------- /src/scss/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | } 4 | .spacer-single { 5 | height: 30px; 6 | } 7 | 8 | .spacer-double { 9 | height: 50px; 10 | } 11 | 12 | // Table 13 | .header { 14 | margin-top: 20px; 15 | } 16 | 17 | .userlist { 18 | margin-top: 50px; 19 | } 20 | 21 | .spinner-border { 22 | width: 10rem; 23 | height: 10rem; 24 | } 25 | 26 | .table_load { 27 | padding-top: 20%; 28 | text-align: center; 29 | } 30 | 31 | .error { 32 | color: red; 33 | } 34 | 35 | // Custom Toastify 36 | .Toastify__toast--success { 37 | color: #07bc0c !important; 38 | border: 1.2px solid #07bc0c !important; 39 | background: transparent !important; 40 | } 41 | 42 | .Toastify__toast--error { 43 | color: red !important; 44 | border: 1.2px solid red !important; 45 | background: transparent !important; 46 | } 47 | 48 | // .Toastify__toast--success::before { 49 | // position: relative; 50 | // z-index: 100000; 51 | // left: 12px; 52 | // top: 6px; 53 | // } 54 | // .Toastify__toast--success::after { 55 | // position: absolute; 56 | // color: #333333; 57 | // font-size: 15px; 58 | // font-weight: 700; 59 | // left: 265px; 60 | // padding-top: 14px !important; 61 | // } 62 | -------------------------------------------------------------------------------- /src/service/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloClient, InMemoryCache } from "@apollo/client"; 2 | 3 | const client = new ApolloClient({ 4 | uri: "http://192.168.115.168:5000/graphql", 5 | // uri: process.env.REACT_APP_BASEURL + "graphql", 6 | cache: new InMemoryCache(), 7 | }); 8 | 9 | export default client; 10 | --------------------------------------------------------------------------------