├── .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 |
233 | {tableLoading ? (
234 |
237 | ) : (
238 |
239 |
240 |
241 | No |
242 | Name |
243 | Gender |
244 | Birthday |
245 | Action |
246 |
247 |
248 |
249 | {userdata.map((item, index) => {
250 | return (
251 |
252 | {index + 1} |
253 | {item.name} |
254 |
255 | {item.gender === 1
256 | ? "Male"
257 | : "Female"}
258 | |
259 |
260 |
264 | |
265 |
266 |
277 |
278 |
287 | |
288 |
289 | );
290 | })}
291 |
292 |
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 |
--------------------------------------------------------------------------------