├── frontend ├── src │ ├── App.css │ ├── vite-env.d.ts │ ├── index.css │ ├── models │ │ ├── Author.ts │ │ └── Books.ts │ ├── ErrorPage.tsx │ ├── components │ │ ├── IconView.tsx │ │ ├── IconDelete.tsx │ │ ├── IconEdit.tsx │ │ ├── DeleteBookModal.tsx │ │ ├── DeleteAuthorModal.tsx │ │ ├── AuthorCreateEditForm.tsx │ │ ├── ViewAuthorModal.tsx │ │ ├── AddEditAuthorModal.tsx │ │ ├── BookCreateEditForm.tsx │ │ ├── ViewBookModal.tsx │ │ └── AddEditBookModal.tsx │ ├── main.tsx │ ├── assets │ │ └── react.svg │ ├── App.tsx │ ├── AuthorsPage.tsx │ └── BooksPage.tsx ├── postcss.config.js ├── public │ ├── ss │ │ ├── books.png │ │ ├── authors.png │ │ └── dashboard.png │ └── vite.svg ├── tailwind.config.js ├── vite.config.ts ├── tsconfig.node.json ├── .gitignore ├── index.html ├── .eslintrc.cjs ├── tsconfig.json ├── package.json ├── README.md └── db.json ├── backend ├── .gitignore ├── server.js ├── configs │ └── db.js ├── package.json ├── app.js ├── routes │ └── index.js ├── controllers │ ├── AuthorsController.js │ └── BooksController.js ├── db.sql └── package-lock.json └── README.md /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | build/ 5 | tmp/ 6 | temp/ 7 | .DS_Store 8 | dist/* -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/ss/books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Learn-It-Right-Way/lirw-react-node-mysql-app/HEAD/frontend/public/ss/books.png -------------------------------------------------------------------------------- /frontend/public/ss/authors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Learn-It-Right-Way/lirw-react-node-mysql-app/HEAD/frontend/public/ss/authors.png -------------------------------------------------------------------------------- /frontend/public/ss/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Learn-It-Right-Way/lirw-react-node-mysql-app/HEAD/frontend/public/ss/dashboard.png -------------------------------------------------------------------------------- /frontend/src/models/Author.ts: -------------------------------------------------------------------------------- 1 | export interface Author { 2 | id: number; 3 | name: string; 4 | birthday: string; 5 | bio: string; 6 | createdAt: string; 7 | updatedAt: string; 8 | } -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | const port = process.env.PORT || 3200; 3 | 4 | app.listen(port, () => { 5 | console.log(`Server is running on port http://localhost:${port}`); 6 | }); -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | content: [ 3 | "./index.html", 4 | "./src/**/*.{js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /backend/configs/db.js: -------------------------------------------------------------------------------- 1 | const mysql = require('mysql2'); 2 | 3 | const db = mysql.createConnection({ 4 | host: 'localhost', 5 | port: '3306', 6 | user: 'root', 7 | password: '12345678', 8 | database: 'react_node_app' 9 | }); 10 | 11 | module.exports = db; -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/ErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import { useRouteError } from "react-router-dom"; 2 | 3 | export default function ErrorPage() { 4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 5 | const error: any = useRouteError(); 6 | 7 | return ( 8 |
9 |

Oops!

10 |

Sorry, an unexpected error has occurred.

11 |

12 | {error.statusText || error.message} 13 |

14 |
15 | ); 16 | } -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mysql-express", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "serve": "pm2 start server.js --interpreter node" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "body-parser": "^1.20.2", 16 | "cors": "^2.8.5", 17 | "express": "^4.19.2", 18 | "mysql2": "^3.10.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/components/IconView.tsx: -------------------------------------------------------------------------------- 1 | export const IconView = () => { 2 | return ( 3 | 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/components/IconDelete.tsx: -------------------------------------------------------------------------------- 1 | export const IconDelete = () => { 2 | return ( 3 | 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /backend/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const mysql = require('mysql2'); 3 | const bodyParser = require('body-parser'); 4 | const routes = require('./routes'); 5 | const cors = require('cors'); 6 | const db = require('./configs/db'); // Import the db connection 7 | 8 | const app = express(); 9 | 10 | app.use(cors()); 11 | app.use(bodyParser.json()); 12 | 13 | db.connect((err) => { 14 | if (err) { 15 | console.error('Error connecting to MySQL: ' + err.stack); 16 | return; 17 | } 18 | console.log('Connected to MySQL Database'); 19 | }); 20 | 21 | // Add your routes here 22 | app.use('/api', routes); 23 | 24 | module.exports = app; -------------------------------------------------------------------------------- /frontend/src/components/IconEdit.tsx: -------------------------------------------------------------------------------- 1 | export const IconEdit = () => { 2 | return ( 3 | 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /backend/routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const booksController = require('../controllers/BooksController'); 3 | const authorsController = require('../controllers/AuthorsController'); 4 | 5 | const router = express.Router(); 6 | 7 | router.get('/authors', authorsController.get); 8 | router.post('/authors', authorsController.create); 9 | router.put('/authors/:id', authorsController.update); 10 | router.delete('/authors/:id', authorsController.delete); 11 | 12 | router.get('/books', booksController.get); 13 | router.post('/books', booksController.create); 14 | router.put('/books/:id', booksController.update); 15 | router.delete('/books/:id', booksController.delete); 16 | 17 | module.exports = router; -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /frontend/src/models/Books.ts: -------------------------------------------------------------------------------- 1 | export interface Book { 2 | id: number; 3 | title: string; 4 | releaseDate: string; 5 | description: string; 6 | pages: number; 7 | createdAt: string; 8 | updatedAt: string; 9 | authorId: number; 10 | name: string; 11 | birthday: string; 12 | bio: string; 13 | } 14 | 15 | export interface BookDTO { 16 | id: number; 17 | title: string; 18 | releaseDate: string; 19 | description: string; 20 | pages: number; 21 | createdAt: string; 22 | updatedAt: string; 23 | author: string 24 | } 25 | 26 | export interface BookFormDTO { 27 | id: number; 28 | title: string; 29 | releaseDate: string; 30 | description: string; 31 | pages: number; 32 | createdAt: string; 33 | updatedAt: string; 34 | author?: number 35 | } -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import { 4 | createBrowserRouter, 5 | RouterProvider, 6 | } from "react-router-dom"; 7 | import App from './App.tsx' 8 | import './index.css' 9 | import ErrorPage from './ErrorPage.tsx'; 10 | import BooksPage from './BooksPage.tsx'; 11 | import AuthorsPage from './AuthorsPage.tsx'; 12 | 13 | const router = createBrowserRouter([ 14 | { 15 | path: "/", 16 | element: , 17 | errorElement: , 18 | }, 19 | { 20 | path: "/books", 21 | element: 22 | }, 23 | { 24 | path: "/authors", 25 | element: 26 | }, 27 | ]); 28 | 29 | ReactDOM.createRoot(document.getElementById('root')!).render( 30 | 31 | 32 | , 33 | ) 34 | -------------------------------------------------------------------------------- /frontend/src/components/DeleteBookModal.tsx: -------------------------------------------------------------------------------- 1 | import { Modal } from "antd"; 2 | import { Book } from "../models/Books"; 3 | 4 | type Props = { 5 | isModalOpen: boolean; 6 | book?: Book; 7 | setIsModalOpen: React.Dispatch>; 8 | onOk: () => void 9 | } 10 | 11 | export const DeleteBookModal = ({ isModalOpen, book, setIsModalOpen, onOk }: Props) => { 12 | const handleOk = () => { 13 | onOk(); 14 | setIsModalOpen(false); 15 | } 16 | 17 | const handleCancel = () => { 18 | setIsModalOpen(false); 19 | } 20 | 21 | return ( 22 | 23 |

Are you sure, you want to delete book {` ${book?.title}`}?

24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/components/DeleteAuthorModal.tsx: -------------------------------------------------------------------------------- 1 | import { Modal } from "antd"; 2 | import { Author } from "../models/Author"; 3 | 4 | type Props = { 5 | isModalOpen: boolean; 6 | author?: Author; 7 | setIsModalOpen: React.Dispatch>; 8 | onOk: () => void 9 | } 10 | 11 | export const DeleteAuthorModal = ({ isModalOpen, author, setIsModalOpen, onOk }: Props) => { 12 | const handleOk = () => { 13 | onOk(); 14 | setIsModalOpen(false); 15 | } 16 | 17 | const handleCancel = () => { 18 | setIsModalOpen(false); 19 | } 20 | 21 | return ( 22 | 23 |

Are you sure, you want to delete author {` ${author?.name}`}?

24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/components/AuthorCreateEditForm.tsx: -------------------------------------------------------------------------------- 1 | import { Form, FormInstance, Input } from "antd" 2 | import { useEffect } from "react"; 3 | import { Author } from "../models/Author"; 4 | 5 | interface Props { 6 | initialValues?: Author; 7 | onFormInstanceReady: (instance: FormInstance) => void; 8 | } 9 | 10 | const { TextArea } = Input; 11 | 12 | export const AuthorCreateEditForm = ({ initialValues, onFormInstanceReady }: Props) => { 13 | const [form] = Form.useForm(); 14 | 15 | useEffect(() => { 16 | onFormInstanceReady(form); 17 | }, []); 18 | 19 | return ( 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |