├── .env
├── .eslintrc
├── .gitignore
├── README.md
├── components
├── BlogPosts.jsx
├── EditModal.jsx
└── SubmitBlogPostForm.jsx
├── graphql
└── queries.js
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.js
├── api
│ ├── graphql.js
│ └── hello.js
└── index.jsx
├── prisma
├── migrations
│ ├── 20210621192720_
│ │ └── migration.sql
│ └── migration_lock.toml
└── schema.prisma
├── public
├── favicon.ico
└── vercel.svg
└── styles
├── Home.module.css
└── globals.css
/.env:
--------------------------------------------------------------------------------
1 | # Environment variables declared in this file are automatically made available to Prisma.
2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#using-environment-variables
3 |
4 | # Prisma supports the native connection string format for PostgreSQL, MySQL, SQL Server and SQLite.
5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
6 |
7 | DATABASE_URL="postgresql://naranoeur:@localhost:5432/twit1?schema=public"
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | The graphql queries from the video
2 |
3 | {blogPosts {id, text}}
4 |
5 | mutation {
6 | addBlogPost(text: "This is the second message!") {
7 | id, text
8 | }
9 | }
10 |
11 | mutation {
12 | editBlogPost(
13 | id: "8c35625c-3198-4fac-9242-46f2a6462239",
14 | text: "Edited"
15 | ) { id, text }
16 | }
17 |
18 | mutation {
19 | deleteBlogPost(id: "8c35625c-3198-4fac-9242-46f2a6462239"){
20 | id, text
21 | }
22 | }
--------------------------------------------------------------------------------
/components/BlogPosts.jsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@apollo/client";
2 | import { GET_BLOGPOSTS } from "../graphql/queries";
3 | import { makeStyles } from "@material-ui/core/styles";
4 | import Card from "@material-ui/core/Card";
5 | import CardActions from "@material-ui/core/CardActions";
6 | import CardContent from "@material-ui/core/CardContent";
7 | import Button from "@material-ui/core/Button";
8 | import Typography from "@material-ui/core/Typography";
9 |
10 | const useStyles = makeStyles({
11 | spacing: {
12 | margin: "15px 0",
13 | },
14 | });
15 |
16 | const BlogPost = ({ text, id, onDelete, openModal }) => {
17 | const classes = useStyles();
18 |
19 | return (
20 |
21 |
22 | {text}
23 |
24 |
25 |
26 |
29 |
32 |
33 |
34 | );
35 | };
36 |
37 | const BlogPosts = ({ onDelete, openModal }) => {
38 | const { loading, error, data } = useQuery(GET_BLOGPOSTS);
39 | if (loading) {
40 | return "loading";
41 | }
42 | if (error) {
43 | return "error";
44 | }
45 |
46 | return data.blogPosts.map((blogPostData) => (
47 |
48 | ));
49 | };
50 |
51 | export default BlogPosts;
52 |
--------------------------------------------------------------------------------
/components/EditModal.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Button from "@material-ui/core/Button";
3 | import TextField from "@material-ui/core/TextField";
4 | import Dialog from "@material-ui/core/Dialog";
5 | import DialogActions from "@material-ui/core/DialogActions";
6 | import DialogContent from "@material-ui/core/DialogContent";
7 | import DialogContentText from "@material-ui/core/DialogContentText";
8 | import DialogTitle from "@material-ui/core/DialogTitle";
9 |
10 | const EditModal = ({ onSubmit, onClose, isOpen }) => (
11 |
27 | );
28 |
29 | export default EditModal;
--------------------------------------------------------------------------------
/components/SubmitBlogPostForm.jsx:
--------------------------------------------------------------------------------
1 | import TextField from "@material-ui/core/TextField";
2 | import Button from "@material-ui/core/Button";
3 |
4 | const SubmitBlogPostForm = ({ onSubmit }) => {
5 | return (
6 |
20 | );
21 | };
22 |
23 | export default SubmitBlogPostForm;
24 |
--------------------------------------------------------------------------------
/graphql/queries.js:
--------------------------------------------------------------------------------
1 | import { gql } from "@apollo/client";
2 |
3 | export const GET_BLOGPOSTS = gql`
4 | {
5 | blogPosts {
6 | id
7 | text
8 | }
9 | }
10 | `;
11 |
12 | export const ADD_BLOGPOST = gql`
13 | mutation AddBlogPost($text: String) {
14 | addBlogPost(text: $text) {
15 | id
16 | text
17 | }
18 | }
19 | `;
20 |
21 | export const DELETE_BLOGPOST = gql`
22 | mutation DeleteBlogPost($id: String) {
23 | deleteBlogPost(id: $id) {
24 | id
25 | text
26 | }
27 | }
28 | `
29 |
30 | export const EDIT_BLOGPOST = gql`
31 | mutation EditBlogPost($id: String, $text: String) {
32 | editBlogPost(id: $id, text: $text) {
33 | id
34 | text
35 | }
36 | }
37 | `
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graphql-blog",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@apollo/client": "^3.3.21",
13 | "@material-ui/core": "^4.12.1",
14 | "@prisma/client": "^2.25.0",
15 | "apollo-server-micro": "2.25.1",
16 | "next": "11.0.0",
17 | "prisma": "^2.25.0",
18 | "react": "17.0.2",
19 | "react-dom": "17.0.2"
20 | },
21 | "devDependencies": {
22 | "eslint": "7.29.0",
23 | "eslint-config-next": "11.0.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
3 |
4 | const client = new ApolloClient({
5 | uri: "http://localhost:3000/api/graphql",
6 | cache: new InMemoryCache(),
7 | });
8 |
9 | function MyApp({ Component, pageProps }) {
10 | return (
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | export default MyApp;
18 |
--------------------------------------------------------------------------------
/pages/api/graphql.js:
--------------------------------------------------------------------------------
1 | import { gql, ApolloServer } from "apollo-server-micro";
2 | import { PrismaClient } from "@prisma/client";
3 |
4 | const prisma = new PrismaClient();
5 |
6 | const typeDefs = gql`
7 | type BlogPost {
8 | id: String
9 | text: String
10 | }
11 |
12 | type Query {
13 | blogPosts: [BlogPost]
14 | }
15 |
16 | type Mutation {
17 | addBlogPost(text: String): BlogPost
18 | editBlogPost(id: String, text: String): BlogPost
19 | deleteBlogPost(id: String): BlogPost
20 | }
21 | `;
22 |
23 | const resolvers = {
24 | Query: {
25 | blogPosts: (_parent, _args, _context) => {
26 | return prisma.blogPost.findMany();
27 | },
28 | },
29 |
30 | Mutation: {
31 | addBlogPost: (_parent, { text }, _context) => {
32 | return prisma.blogPost.create({ data: { text } });
33 | },
34 | editBlogPost: (_parent, { id, text }, _context) => {
35 | return prisma.blogPost.update({ where: { id }, data: { text } });
36 | },
37 | deleteBlogPost: (_parent, { id }, _context) => {
38 | return prisma.blogPost.delete({ where: { id } });
39 | },
40 | },
41 | };
42 |
43 | const apolloServer = new ApolloServer({ typeDefs, resolvers });
44 |
45 | const handler = apolloServer.createHandler({ path: "/api/graphql" });
46 |
47 | export const config = { api: { bodyParser: false } };
48 |
49 | export default handler;
50 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function handler(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import { useState } from "react";
3 | import { useMutation } from "@apollo/client";
4 | import {
5 | ADD_BLOGPOST,
6 | DELETE_BLOGPOST,
7 | EDIT_BLOGPOST,
8 | } from "../graphql/queries";
9 | import BlogPosts from "../components/BlogPosts";
10 | import Container from "@material-ui/core/Container";
11 | import SubmitBlogPostForm from "../components/SubmitBlogPostForm";
12 | import EditModal from "../components/EditModal";
13 |
14 | export default function Home() {
15 | const [addBlogPost] = useMutation(ADD_BLOGPOST, {
16 | onCompleted: (data) => {
17 | window.location.reload();
18 | },
19 | });
20 |
21 | const onSubmit = (e) => {
22 | e.preventDefault();
23 | addBlogPost({ variables: { text: e.target.text.value } });
24 | };
25 |
26 | const [deleteBlogPost] = useMutation(DELETE_BLOGPOST, {
27 | onCompleted: (data) => {
28 | window.location.reload();
29 | },
30 | });
31 |
32 | const onDelete = (id) => deleteBlogPost({ variables: { id } });
33 |
34 | const [editId, setEditId] = useState("");
35 |
36 | const onClose = () => setEditId("");
37 | const openModal = (id) => setEditId(id);
38 |
39 | const [editBlogPost] = useMutation(EDIT_BLOGPOST, {
40 | onCompleted: () => setEditId(""),
41 | });
42 |
43 | const onSaveEdit = (e) => {
44 | e.preventDefault();
45 | editBlogPost({ variables: { id: editId, text: e.target.text.value } });
46 | };
47 |
48 | return (
49 |
50 |
51 | Create Next App
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/prisma/migrations/20210621192720_/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "BlogPost" (
3 | "id" TEXT NOT NULL,
4 | "text" TEXT NOT NULL,
5 |
6 | PRIMARY KEY ("id")
7 | );
8 |
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | datasource db {
5 | provider = "postgresql"
6 | url = env("DATABASE_URL")
7 | }
8 |
9 | generator client {
10 | provider = "prisma-client-js"
11 | }
12 |
13 | model BlogPost {
14 | id String @id @default(uuid())
15 | text String
16 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pheezx/Graphql-Blog/b54f3b03621897938bf19f57090a96e928a6e14e/public/favicon.ico
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | height: 100vh;
9 | }
10 |
11 | .main {
12 | padding: 5rem 0;
13 | flex: 1;
14 | display: flex;
15 | flex-direction: column;
16 | justify-content: center;
17 | align-items: center;
18 | }
19 |
20 | .footer {
21 | width: 100%;
22 | height: 100px;
23 | border-top: 1px solid #eaeaea;
24 | display: flex;
25 | justify-content: center;
26 | align-items: center;
27 | }
28 |
29 | .footer a {
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | flex-grow: 1;
34 | }
35 |
36 | .title a {
37 | color: #0070f3;
38 | text-decoration: none;
39 | }
40 |
41 | .title a:hover,
42 | .title a:focus,
43 | .title a:active {
44 | text-decoration: underline;
45 | }
46 |
47 | .title {
48 | margin: 0;
49 | line-height: 1.15;
50 | font-size: 4rem;
51 | }
52 |
53 | .title,
54 | .description {
55 | text-align: center;
56 | }
57 |
58 | .description {
59 | line-height: 1.5;
60 | font-size: 1.5rem;
61 | }
62 |
63 | .code {
64 | background: #fafafa;
65 | border-radius: 5px;
66 | padding: 0.75rem;
67 | font-size: 1.1rem;
68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
69 | Bitstream Vera Sans Mono, Courier New, monospace;
70 | }
71 |
72 | .grid {
73 | display: flex;
74 | align-items: center;
75 | justify-content: center;
76 | flex-wrap: wrap;
77 | max-width: 800px;
78 | margin-top: 3rem;
79 | }
80 |
81 | .card {
82 | margin: 1rem;
83 | padding: 1.5rem;
84 | text-align: left;
85 | color: inherit;
86 | text-decoration: none;
87 | border: 1px solid #eaeaea;
88 | border-radius: 10px;
89 | transition: color 0.15s ease, border-color 0.15s ease;
90 | width: 45%;
91 | }
92 |
93 | .card:hover,
94 | .card:focus,
95 | .card:active {
96 | color: #0070f3;
97 | border-color: #0070f3;
98 | }
99 |
100 | .card h2 {
101 | margin: 0 0 1rem 0;
102 | font-size: 1.5rem;
103 | }
104 |
105 | .card p {
106 | margin: 0;
107 | font-size: 1.25rem;
108 | line-height: 1.5;
109 | }
110 |
111 | .logo {
112 | height: 1em;
113 | margin-left: 0.5rem;
114 | }
115 |
116 | @media (max-width: 600px) {
117 | .grid {
118 | width: 100%;
119 | flex-direction: column;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------