├── .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 | 12 | Edit Blog Post 13 |
14 | 15 | 16 | 17 | 18 | 21 | 24 | 25 |
26 |
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 |
7 | 16 | 19 | 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 | 3 | 4 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------