풀 스택 React 및 Node.js - CRUD
node-server 폴더에서 note.model.js를 다음과 같이 편집합니다.
const { prisma } = require("./db")
async function getNotes() {
return prisma.note.findMany()
}
async function getNote(id) {
return prisma.note.findUnique({ where: { id } })
}
async function createNote(
note
) {
return prisma.note.create({
data: note
})
}
async function updateNote(
id, note
) {
return prisma.note.update({
data: note,
where: {
id
}
})
}
async function deleteNote(
id
) {
return prisma.note.delete({
where: {
id
}
})
}
module.exports = {
getNotes,
getNote,
createNote,
updateNote,
deleteNote,
}
node-server 폴더에서 note.controller.js를 다음과 같이 편집합니다.
const authorRepo = require('../models/author.model');
const noteRepo = require('../models/note.model');
async function getNotes(req, res) {
const notes = await noteRepo.getNotes();
res.json({
notes
});
}
async function getNote(req, res) {
const {id} = req.params;
const note = await noteRepo.getNote(id);
const { authorId, ...noteRest } = note;
const { username } = await authorRepo.getAuthor(authorId);
res.json({ note: {
...noteRest,
author: username
}
});
}
async function retrieveOrCreateAuthor(username) {
let author = await authorRepo.getAuthorByName(username);
if (author === null) {
author = await authorRepo.createAuthor({
username
})
}
return author
}
async function postNote(req, res) {
const {body} = req;
const {title, content, author, lang, isLive, category} = body;
try {
const noteAuthor = await retrieveOrCreateAuthor(author);
const note = await noteRepo.createNote({
title,
content,
lang,
isLive,
category,
authorId: noteAuthor.id
})
res
.status(200)
.json({
note
})
} catch (e) {
console.error(e);
res.status(500).json({error: "Something went wrong"})
}
}
async function putNote(req, res) {
const {body} = req;
const {id, title, content, author, lang, isLive, category} = body;
try {
const noteAuthor = await retrieveOrCreateAuthor(author);
const note = await noteRepo.updateNote(id, {
title,
content,
lang,
isLive,
category,
authorId: noteAuthor.id
})
res
.status(200)
.json({
note
})
} catch (e) {
console.error(e);
res.status(500).json({error: "Something went wrong"})
}
}
async function deleteNote(req, res) {
const {body} = req;
const {id} = body;
try {
await noteRepo.deleteNote(id)
res
.status(200).send()
} catch (e) {
console.error(e);
res.status(500).json({error: "Something went wrong"})
}
}
module.exports = {
getNotes,
getNote,
postNote,
putNote,
deleteNote,
}
node-server에서 route/index.js를 다음과 같이 편집합니다.
const express = require('express');
const noteRouter = express.Router();
const noteController = require('../controllers/note.controller');
noteRouter.get('/', noteController.getNotes);
noteRouter.get('/:id', noteController.getNote);
noteRouter.post('/', noteController.postNote);
noteRouter.put('/', noteController.putNote);
noteRouter.delete('/', noteController.deleteNote);
const routes = app => {
app.use('/note', noteRouter);
};
module.exports = routes
서버 측에는 이제 기본 CRUD 작업에 필요한 모든 작업이 있습니다.
Create, Read, Update, Delete
지금 클라이언트와 서버를 실행해 보십시오. 양식에서 제출 버튼을 클릭하면 두 가지 문제가 있음을 알 수 있습니다. 첫째, 양식이 응답하지 않고 반복해서 클릭해도 어떤 일이 발생했는지 알 수 없습니다. 둘째, 서버 콘솔을 보면 오류가 있음을 알 수 있습니다.
Argument isLive: Got invalid value 'true' on prisma.createOneNote. Provided String, expected Boolean.
isLive
는 부울이지만 Prisma에 문자열로 전송됩니다.node-server index.js에서 다음을 사용합니다.
app.use(bodyParser.json());
이것은 실제로 구문 분석 중에 올바른 유형을 검색하므로 문제는 클라이언트에 있어야 합니다.
onSubmit
핸들러에서 Form.js의 입력 제어 데이터를 수집할 때 항상 문자열을 반환하는 input.value
를 사용합니다.Form.js를 다음과 같이 편집합니다.
import React, {useState} from 'react';
import InputLabel from "./InputLabel";
import {isEmptyString, isNullOrUndefined, titleFromName} from "./strings";
import './form.css'
const Form = ({entity, onSubmitHandler, onDeleteHandler}) => {
const [isSubmitting, setIsSubmitting] = useState(false);
return (
<form onSubmit={e => {
setIsSubmitting(true);
const form = e.target;
const newEntity = Object.values(form).reduce((obj, field) => {
const {name} = field;
if (!isEmptyString(name)) {
switch (typeof entity[name]) {
case "number":
obj[name] = field.valueAsNumber;
break;
case "boolean":
obj[name] = field.value === 'true';
break;
default:
obj[name] = field.value
}
}
return obj
}, {})
onSubmitHandler(newEntity);
e.stopPropagation();
e.preventDefault()
}}>
<fieldset
disabled={isSubmitting}
>
{
Object.entries(entity).map(([entityKey, entityValue]) => {
if (entityKey === "id") {
return <input
type="hidden"
name="id"
key="id"
value={entityValue}
/>
} else {
return <InputLabel
id={entityKey}
key={entityKey}
label={titleFromName(entityKey)}
type={
typeof entityValue === "boolean"
? "checkbox"
: "text"
}
value={entityValue}
/>
}
})
}
</fieldset>
<button
type="submit"
disabled={isSubmitting}
>
{
isSubmitting ? 'Submitting' : 'Submit'
}
</button>
{
onDeleteHandler && !isNullOrUndefined(entity.id) && <button
disabled={isSubmitting}
onClick={() => {
setIsSubmitting(true);
onDeleteHandler(entity.id)
}}
>
Delete
</button>
}
</form>
);
};
export default Form;
변경 사항:
양식을 다시 저장하면 버그가 수정되었음을 알 수 있습니다.
나머지 CRUD 작업을 구현하기 전에 작은 리팩터링이 필요합니다. react-client에서 .env.development를 생성합니다.
REACT_APP_URL_API=http://localhost:4011/
useFetch.js를 생성합니다.
import {useState, useEffect} from "react";
export const getUrl = url => new URL(url, process.env.REACT_APP_URL_API).toString();
function useFetch(url, skip) {
const [data, setData] = useState({});
useEffect( () => {
const abortController = new AbortController();
async function fetchData() {
const fullUrl = getUrl(url);
console.log('Fetching from: ' + fullUrl);
try {
const response = await fetch(fullUrl, {
signal: abortController.signal,
});
if (response.ok) {
console.log('Response received from server and is ok!')
const res = await response.json();
if (abortController.signal.aborted) {
console.log('Abort detected, exiting!')
return;
}
setData(res)
}
} catch(e) {
console.log(e)
}
}
!skip && fetchData()
return () => {
console.log('Aborting GET request.')
abortController.abort();
}
}, [url, setData, skip])
return data
}
export default useFetch
현재 양식은 새 메모를 추가만 할 수 있으며 편집은 할 수 없습니다. 몇 가지 작업을 수행해야 합니다.
AddEditNote.js를 다음과 같이 리팩터링:
import React from 'react';
import {useParams, useNavigate} from "react-router-dom";
import RenderData from "./RenderData";
import Form from './Form';
import useFetch, {getUrl} from "./useFetch";
import {isNullOrUndefined} from "./strings";
const AddEditNote = () => {
const {noteId} = useParams();
const {note = {
title: '',
content: '',
lang: '',
isLive: false,
category: '',
author: '',
}} = useFetch('note/' + noteId, isNullOrUndefined(noteId));
const navigate = useNavigate();
return (
<div>
<RenderData
data={note}
/>
<Form
entity={note}
onSubmitHandler={async newNote => {
console.log({newNote})
const response = await fetch(getUrl('note'), {
method: isNullOrUndefined(newNote.id) ? 'POST' : 'PUT',
body: JSON.stringify(newNote),
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
await response.json()
navigate('/note-list')
}
}}
onDeleteHandler={async (id) => {
if (!isNullOrUndefined(id)) {
await fetch(getUrl('note'), {
method: 'DELETE',
body: JSON.stringify({id}),
headers: {
'Content-Type': 'application/json'
}
});
navigate('/note-list')
}
}}
/>
</div>
);
};
export default AddEditNote;
반응 클라이언트에서 TableList.js 만들기
import React from 'react';
import {titleFromName} from './strings';
import './table-list.css';
const TableList = ({
data,
title,
onClickHandler,
idField = 'id',
fieldFormatter = {},
}) => {
if (!data || data.length === 0) {
return null
}
const firstRow = data[0];
const dataColumnNamesToRender = Object.getOwnPropertyNames(firstRow)
.filter(propName => propName !== idField);
const headerRow = dataColumnNamesToRender.map((propName, i) => <th
key={i}
>
{
titleFromName(propName)
}
</th>);
return (
<table>
<caption>
{
title
}
</caption>
<thead>
<tr>
{
headerRow
}
</tr>
</thead>
<tbody>
{
data.map((dataRow, i) => <tr
key={i}
onClick={() => onClickHandler && onClickHandler(dataRow[idField])}
>
{
dataColumnNamesToRender.map((dataColumnName, i) => <td
key={i}
>
{
(fieldFormatter[dataColumnName] ?? (v => v))(dataRow[dataColumnName], dataRow)
}
</td>)
}
</tr>)
}
</tbody>
</table>
);
};
export default TableList;
react-client에서 table-list.css 생성
table {
margin: 12px;
border-collapse: collapse;
}
th {
color: white;
padding: 8px;
background-color: #444;
}
td {
border-bottom: 1px solid #ddd;
padding: 12px;
}
td a,
td a:visited {
color: black;
}
td:not(:last-child) {
border-left:1px solid #ccc;
border-right: 1px solid #ccc;
}
tr:nth-child(even) {
background-color: #f1f1f1;
}
일반 양식 구성 요소와 마찬가지로 일반 데이터 목록 구성 요소입니다.
반응 클라이언트에서 NoteList.js 만들기
import React from 'react';
import TableList from "./TableList";
import {Link} from "react-router-dom";
import useFetch from "./useFetch";
const NoteList = () => {
const {notes} = useFetch('note')
return (
<TableList
data={notes}
fieldFormatter={{
title: (title, dataRow) => [
<Link
to={`/edit-note/${dataRow.id}`}
key='1'
>
edit
</Link>,
<span key="2">
{
title
}
</span>
],
dateCreated: date => new Date(date).toLocaleString()
}}
/>
);
};
export default NoteList;
이것은 TableList.js를 사용하여 메모를 나열합니다.
마지막으로 App.js를 다음과 같이 변경합니다.
import {
Link,
HashRouter as Router,
Routes,
Route,
} from 'react-router-dom';
import AddEditNote from "./AddEditNote";
import NoteList from "./NoteList";
import './App.css';
function App() {
return (
<div className="App">
<Router>
<Routes>
<Route exact path="/" element={
<ul>
<li>
<Link to="/note-list">List Notes</Link>
</li>
<li>
<Link to="/edit-note">Create Note</Link>
</li>
</ul>
}/>
<Route path="/note-list" element={<NoteList/>}/>
<Route path="/edit-note" element={<AddEditNote/>}/>
<Route path="/edit-note/:noteId" element={<AddEditNote/>}/>
</Routes>
</Router>
</div>
);
}
export default App;
지금 실행하면 모든 기본 CRUD 작업이 작동합니다.
축하합니다, 풀 스택.
이 앱에는 양식 유효성 검사 및 날짜 처리, 드롭다운 목록과 같은 몇 가지 사항이 없습니다. 그러나 이것들은 쉽게 추가할 수 있어야 합니다...
코드 저장소: Github Repository
Reference
이 문제에 관하여(풀 스택 React 및 Node.js - CRUD), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/neohed/full-stack-react-nodejs-crud-2328텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)