😁Study MERN CRUD applications Todo App Back End , Front End
PreviousMERN CRUD applications Todo App Back End , Front End (ok)NextTạo bảng và thêm dữ liệu vào mysql giống migate của Laravel (ok)
Last updated
Last updated
Project Start 👇
src\server\db\index.ts
import * as todos from './tables/todos';
export default {
todos
}
src\server\db\connection.ts
import mysql from "mysql2/promise";
export default mysql.createPool({
host: 'localhost',
user: 'root',
password: '',
database: 'todo_app'
});
src\server\db\queryUtils.ts
import pool from './connection';
import type { ResultSetHeader } from 'mysql2';
export async function SelectQuery<T>(queryString: string, params?: any) {
const [results] = await pool.execute(queryString, params);
return results as T[];
}
export async function ModifyQuery<T>(queryString: string, params?: any) {
const [results] = await pool.query(queryString, params);
return results as T[];
}
src\server\db\tables\todos.ts
import {
SelectQuery,
ModifyQuery
} from '../queryUtils';
export interface ITodosRow {
id: number;description: string;isCompleted: 0 | 1;
}
export function getAll() {
return SelectQuery < ITodosRow > ('SELECT * FROM todos;');
}
export function getOne(id: number) {
return SelectQuery < ITodosRow > ('SELECT * FROM todos WHERE id = ?;', [id]);
}
export function insert(todoItem: string) {
return ModifyQuery('INSERT INTO todos (description) VALUE (?);', [todoItem])
}
export function update(id: number, description: string, isCompleted: number) {
return ModifyQuery('UPDATE todos SET description=?,isCompleted=? WHERE id=?;', [description, isCompleted, id]);
}
src\server\routes\index.ts
import {
Router
} from 'express';
import todosRouter from './todos';
const router = Router();
router.use('/todos', todosRouter);
export default router;
src\server\routes\todos.ts
import {
Router
} from 'express';
import db from '../db';
const router = Router();
router.get('/', async (req, res) => {
try {
const todos = await db.todos.getAll();
res.json(todos);
} catch (error) {
console.log(error);
res.status(500).json({
message: 'Internal Server Error',
error
});
}
});
export default router;
src\server\server.ts
import express from 'express';
import cors from "cors";
import apiRouter from './routes';
const app = express();
const corsOptions = {
origin: 'http://localhost:9000',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
app.use(express.json());
const port = process.env.PORT || 3000;
var hostct = 'localhost';
app.use('/api', apiRouter);
app.listen(port, () => console.log(`Server running on port http://${hostct}:${port}`));
src\client\App.tsx
import React from "react";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import Home from "./views/Home";
import Todos from "./views/Todos";
interface AppProps { }
const App = (props: AppProps) => {
return (
<BrowserRouter>
<div className="px-5 py-2">
<Link to="/">Home</Link>
</div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/todos" element={<Todos />} />
</Routes>
</BrowserRouter>
);
}
export default App;
src\client\views\Todos.tsx
import React, { useState, useEffect } from "react";
import { fetchData } from '../services/fetchData';
import { ITodo } from "../types";
import { Link } from "react-router-dom";
interface TodoProps { }
const Todos = (props: TodoProps) => {
const [list, setList] = useState<ITodo[]>([]);
useEffect(() => {
fetchData('/api/todos').
then(list => setList(list));
},[]);
return (
<main className="container mt-5" >
<section className="row justify-content-center" >
<div className="col-12 col-md-6">
<ul className="list-group">
{list.map(todo => (
<li key={`todo-item-${todo.id}`} className="list-group-item d-flex justify-content-between align-items-center">
<span>{todo.description}</span>
<Link to={`/todos/${todo.id}`} className="btn btn-small btn-secondary">Details</Link>
</li>
))}
</ul>
</div>
</section>
</main>
);
}
export default Todos;
src\client\types\index.ts
export interface ITodo {
id: number;
description: string;
isCompleted: 0 | 1;
}
src\client\services\fetchData.ts
const BASE_URL = process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : '';
export async function fetchData(endpoint: string, method: string = "GET", payload: any = null) {
try {
const options: RequestInit = {
method,
headers: {}
}
if (payload && method != "GET") {
options.headers = {
'Content-Type': 'application/json'
}
options.body = JSON.stringify(payload);
}
const response = await fetch(`${BASE_URL}${endpoint}`, options);
if (!response.ok) throw new Error(`HTTP Error Status: ${response.status}`);
const todos = await response.json();
return todos;
} catch (error) {
}
}
router.get('/:id', async (req, res) => {});
src\server\routes\todos.ts
import {
Router
} from 'express';
import db from '../db';
const router = Router();
router.get('/', async (req, res) => {
try {
const todos = await db.todos.getAll();
res.json(todos);
} catch (error) {
console.log(error);
res.status(500).json({
message: 'Internal Server Error',
error
});
}
});
router.get('/:id', async (req, res) => {
try {
const id = parseInt(req.params.id, 10);
const [todo] = await db.todos.getOne(id);
res.json(todo);
} catch (error) {
console.log(error)
res.status(500).json({ message: 'Internal Server Error', error });
}
});
export default router;
src\client\App.tsx
import React from "react";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import Home from "./views/Home";
import Todos from "./views/Todos";
import Details from "./views/Details";
interface AppProps {}
const App = (props: AppProps) => {
return (
<BrowserRouter>
<div className="px-5 py-2">
<Link to="/">Home</Link>
</div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/todos" element={<Todos />} />
<Route path="/todos/:id" element={<Details/>} />
</Routes>
</BrowserRouter>
);
}
export default App;
src\client\views\Details.tsx
import React, { useState, useEffect } from 'react';
import { Link, useParams } from 'react-router-dom';
import type { ITodo } from '../types';
import { fetchData } from '../services/fetchData';
interface DetailsProps { }
const Details = (props: DetailsProps) => {
const { id } = useParams();
const [data, setData] = useState<ITodo | null>(null);
useEffect(() => {
fetchData(`/api/todos/${id}`).then(data => setData(data));
}, []);
return (
<main className="container mt-5" >
<section className="row justify-content-center" >
<div className="col-12 col-md-6">
<div className="card shadow">
<div className="card-body">
<h2 className="card-title">Todo Item # {id}</h2>
<p className="card-text">{data?.description}</p>
<Link to="/todos" className='btn btn-outline-info'>Go Back</Link>
</div>
</div>
</div>
</section>
</main>
);
};
export default Details;
src\server\routes\todos.ts
import {
Router
} from 'express';
import db from '../db';
import { ResultSetHeader } from 'mysql2';
const router = Router();
router.get('/', async (req, res) => {
try {
const todos = await db.todos.getAll();
res.json(todos);
} catch (error) {
console.log(error);
res.status(500).json({
message: 'Internal Server Error',
error
});
}
});
router.get('/:id', async (req, res) => {
try {
const id = parseInt(req.params.id, 10);
const [todo] = await db.todos.getOne(id);
res.json(todo);
} catch (error) {
console.log(error)
res.status(500).json({ message: 'Internal Server Error', error });
}
});
router.post('/', async (req, res) => {
try {
const newTodo = req.body;
const result:any = await db.todos.insert(newTodo.description);
res.json({ message: 'Todo created', id: result.insertId });
} catch (error) {
console.log(error)
res.status(500).json({ message: 'Internal Server Error', error });
}
});
export default router;
src\client\App.tsx
import React from "react";
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import Home from "./views/Home";
import Todos from "./views/Todos";
import Details from "./views/Details";
import Add from "./views/Add";
interface AppProps {}
const App = (props: AppProps) => {
return (
<BrowserRouter>
<div className="px-5 py-2">
<Link to="/">Home</Link>
<Link to="/todos" className="mx-2">Todos</Link>
<Link to="/todos/add" className="mx-2">Add</Link>
</div>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/todos" element={<Todos />} />
<Route path="/todos/:id" element={<Details/>} />
<Route path="/todos/add" element={<Add />} />
</Routes>
</BrowserRouter>
);
}
export default App;
src\client\views\Add.tsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { fetchData } from '../services/fetchData';
interface AddProps { }
const Add = (props: AddProps) => {
const navigate = useNavigate();
const [value, setValue] = useState<string>('');
const handleSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
fetchData('/api/todos', 'POST', { description: value }).then(data =>
navigate(`/todos/${data.id}`)
);
};
return (
<main className="container mt-5" >
<section className="row justify-content-center" >
<div className="col-12 col-md-6">
<form className="p-4 shadow border">
<label htmlFor="description">Todo Item Description</label>
<input value={value} onChange={e => setValue(e.target.value)} type="text" className="form-control" placeholder="Do All The Things" />
<button onClick={handleSubmit} className="mt-3 btn btn-primary">Add Todo</button>
</form>
</div>
</section>
</main>
);
};
export default Add;
src\server\routes\todos.ts
import {
Router
} from 'express';
import db from '../db';
import { log } from 'console';
const router = Router();
router.get('/', async (req, res) => {
try {
const todos = await db.todos.getAll();
res.json(todos);
} catch (error) {
console.log(error);
res.status(500).json({
message: 'Internal Server Error',
error
});
}
});
router.get('/:id', async (req, res) => {
let id = parseInt(req.params.id);
try {
const [todo] = await db.todos.getOne(id);
res.json(todo);
} catch (error) {
console.log(error);
res.status(500).json({
message: 'Internal Server Error',
error
});
}
});
router.post('/', async (req, res) => {
const result:any = await db.todos.insert(req.body.description);
res.json({message: 'Todo created',id: result.insertId});
});
router.put('/', async (req, res) => {
try {
console.log(req.body);
const newTodo = req.body;
const result = await db.todos.update(newTodo.id,newTodo.description,newTodo.isCompleted);
res.json({ message: 'Update created', id: result })
} catch (error) {
console.log(error)
res.status(500).json({ message: 'Internal Server Error', error });
}
});
export default router;
src\client\services\fetchData.ts
const BASE_URL = process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : '';
export async function fetchData(endpoint: string, method: string = 'GET', payload: any =null) {
try {
const options: RequestInit = {
method,
headers: {}
}
if (payload && method !== 'GET') {
options.headers = {
'Content-type': 'application/json'
};
options.body = JSON.stringify(payload);
}
const response = await fetch(`${BASE_URL}${endpoint}`, options);
if (!response.ok) {
throw new Error(`HTTP Error Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Fetch error: ${error}`);
throw error;
}
}
src\server\db\tables\todos.ts
import {
SelectQuery,
ModifyQuery
} from '../queryUtils';
export interface ITodosRow {
id: number;
description: string;
isCompleted: 0 | 1;
}
export function getAll() {
return SelectQuery < ITodosRow > ('SELECT * FROM todos;');
}
export function getOne(id: number) {
return SelectQuery < ITodosRow > ('SELECT * FROM todos WHERE id = ?;', [id]);
}
export function insert(todoItem: string) {
return ModifyQuery('INSERT INTO todos (description) VALUE (?);', [todoItem])
}
export function update(id: number, description: string, isCompleted: number) {
return ModifyQuery('UPDATE todos SET description=?,isCompleted=? WHERE id=?;', [description, isCompleted, id]);
}
src\client\views\Edit.tsx
import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { fetchData } from '../services/fetchData';
import { ITodo } from "../types/ITodo";
interface AddProps { }
const Edit = (props: AddProps) => {
const navigate = useNavigate();
const { id } = useParams();
const [InputDes, setDataName] = useState("");
const [InputIs, setDataIs] = useState(1);
const handleNameChange = (name:string) => {
setDataName(name);
};
const handleNumberChange = (isCom:any) => {
setDataIs(isCom);
};
useEffect(() => {
fetchData(`/api/todos/${id}`).then(data => {
console.log(data);
setDataName(data.description);
setDataIs(data.isCompleted);
});
}, []);
const handleSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
fetchData('/api/todos', 'PUT', {id: id, description:InputDes,isCompleted:InputIs}).then(dat =>
{
navigate(`/todos/${id}`);
}
);
};
return (
<main className="container mt-5" >
<section className="row justify-content-center" >
<div className="col-12 col-md-6">
<form className="p-4 shadow border">
<label htmlFor="description">Todo Item Description</label>
<input id="description" name="description" value={InputDes} type="text" onChange={e => handleNameChange(e.target.value)} className="form-control" placeholder="Do All The Things" />
<label htmlFor="isCompleted">Todo Item isCompleted</label>
<input id="isCompleted" name="iscompleted" value={InputIs} type="number" onChange={e => handleNumberChange(e.target.value)} className="form-control" placeholder="Do All The Things" />
<button onClick={handleSubmit} className="mt-3 btn btn-primary">Edit Todo</button>
</form>
</div>
</section>
</main>
);
}
export default Edit;