한 문장으로 여러사람들에게 동기부여와 여러 메세지들을 담을 수 있는 공간을 제작하고 싶었다.
개인 프로젝트를 제작하기 위해 서버를 구축하는 법부터 알아야했다.
Node, Express, MongoDB, Mongoose 의 사용법을 어느정도 알고 제작을 시작했다.
Javascript하나로 프론트와 서버를 직접 만들수 있기 때문에 도전해봤다.
- Express : 웹 서버 생성용 node전용 웹 프레임워크
- MongoDB : 데이터들을 저장할 NoSQL 데이터베이스 (NoSQL : 관계형 데이터베이스가 아닌 SQL)
- Mongoose : node와 MongoDB를 연동해줄 라이브러리
MongoDB 연동하기
MongoDB Atlas 회원가입과 클러스터 및 컬렉션 생성은 여러 블로그와 영상을 찾아보면서 생성했다.
프로젝트 백엔드 루트에서 express와 mongoose를 설치하고 연결을 시작했다.
require('dotenv').config();
const cors = require('cors');
const path = require('path');
const mongoose = require('mongoose');
const express = require('express');
const useRouter = require('./routes/index.js');
const bodyParser = require('body-parser');
const app = express();
const db = mongoose.connection;
app.use(bodyParser.json());
app.use(express.json());
app.use(express.static(path.join(__dirname, '')));
app.use(cors({ credentials: true }));
/* express 서버 오픈 */
app.listen(process.env.PORT, () => {
console.log(process.env.PORT, '포트 서버오픈');
});
/* Mongoose, MongoDB 연결 */
try {
mongoose.set('strictQuery', false);
mongoose.connect(process.env.DB_URL, { useNewUrlParser: true, useUnifiedTopology: true });
mongoose.connection.once('open', () => {
console.log('MongoDB 연결 완료');
});
} catch (error) {
console.error('MongoDB 에러');
console.log(error);
}
import mongoose from 'mongoose';
const connect = () => {
mongoose.connect('localhost:27017', (err) => {
if (err) console.error('mongodb 연결 에러', err);
console.log('mongodb 연결성공');
});
};
connect();
mongoose.connection.on('disconnected', connect);
export default connect;
node app.js // default
npx nodemon app.js // nodemon 사용시
성공적으로 연결 된 모습이다.
CRUD 테스트
테스트를 위해 MongoDB용 API와 스키마를 사용한 API로 CRUD를 해봤다.
테스트는 Thunder Client로 진행했다.
app.get('url', (req, res) => {
res.json({ key: 'value' });
});
/* Create */
app.post('/create', (req, res) => {
db.collection('컬렉션이름').insertOne({ Object }, MiddleWare, (err, result) => {
if (err) {
res.status(500).json({
message: err,
});
} // 에러
console.log('Create 완료');
res.status(200).json({
message: 'Create success',
data: {
post: result,
},
});
});
});
/* Read All */
app.get('/read-all', (req, res) => {
db.collection('컬렉션이름').find({ Object }, MiddleWare, (err, result) => {
if (!result) return res.status(404).json({ message: 'data not found' }); // 결과 없을 때
if (err) {
res.status(500).json({
message: err,
});
} // 에러
console.log('Read All 완료');
res.status(200).json({
message: 'Read All success',
data: {
post: result,
},
});
});
});
/* Read Detail */
app.get('/read-deatil', (req, res) => {
db.collection('컬렉션이름').findOne({ Object }, MiddleWare, (err, result) => {
if (!result) return res.status(404).json({ message: 'data not found' }); // 결과 없을 때
if (err) {
res.status(500).json({
message: err,
});
} // 에러
console.log('Read Detail 완료');
res.status(200).json({
message: 'Read Detail success',
data: {
post: result,
},
});
});
});
/* Update */
app.put('/:id', (req, res) => {
db.collection('컬렉션이름').updateOne({ Object }, { operator: { Object } }, (err, result) => {
if (!result) return res.status(404).json({ message: 'data not found' }); // 결과 없을 때
if (err) {
res.status(500).json({
message: err,
});
} // 에러
console.log('Update 완료');
res.status(200).json({
message: 'Update success',
data: {
post: result,
},
});
});
});
/* Delete */
app.delete('url', (req, res) => {
db.collection('컬렉션이름').deleteOne({ Object }, MiddleWare, (err, result) => {
if (!result) return res.status(404).json({ message: 'data not found' }); // 결과 없을 때
if (err) {
res.status(500).json({
message: err,
});
} // 에러
console.log('Delete 완료');
/* 여러가지 응답 방법 */
res.status(200).json({
message: 'Delete success',
}); // 성공
res.status(404).json({
message: 'Delete failed',
}); // 실패
res.send('message'); // 메시지 보내기
res.sendFile('file'); // 파일 보내기
res.json('json'); // json 데이터 보내기
res.render('ejs'); // ejs파일 렌더링
});
});
const express = require('express');
const testSchema = require('../model/testSchema');
const useRouter = express.Router();
/* Create */
useRouter.post('/create', (req, res) => {
const { title, author } = req.body; // 구조분해할당
console.log(req.body);
const testModel = new testSchema();
testModel.title = title;
testModel.author = author;
testModel
.save()
.then((testMessage) => {
console.log('Create 완료');
res.status(200).json({
message: 'Create success',
data: {
post: testMessage,
},
});
})
.catch((err) => {
res.status(500).json({
message: err,
});
});
});
/* Read All */
useRouter.get('/read-all', (req, res) => {
testSchema
.find() // 광범위하게 읽기
.then((testMessage) => {
console.log('Read All 완료');
res.status(200).json({
message: 'Read All success',
data: {
post: testMessage,
},
});
})
.catch((err) => {
res.status(500).json({
message: err,
});
});
});
/* Read Detail */
useRouter.get('/:post_id', (req, res) => {
const postId = req.params.post_id;
testSchema
.findOne({ _id: postId })
.then((testModel) => {
if (!testModel) return res.status(404).json({ message: 'post not found' });
console.log('Read Detail 완료');
res.status(200).json({
message: 'Read Detail success',
data: {
post: testModel,
},
});
})
.catch((err) => {
res.status(500).json({
message: err,
});
});
});
/* Update */
useRouter.put('/:post_id', (req, res) => {
const post_id = req.params.post_id;
const { title, content } = req.body;
testSchema
.findById(post_id)
.then((testModel) => {
if (!testModel) return res.status(404).json({ message: 'post not found' });
testModel.title = title;
testModel.content = content;
testModel.save().then((output) => {
console.log('Update 완료');
res.status(200).json({
message: 'Update success',
data: {
post: output,
},
});
});
})
.catch((err) => {
res.status(500).json({
message: err,
});
});
});
/* Delete */
useRouter.delete('/:post_id', (req, res) => {
const post_id = req.params.post_id;
testSchema
.deleteOne({ _id: post_id })
.then((output) => {
if (output.n == 0) return res.status(404).json({ message: 'post not found' });
console.log('Delete 완료');
res.status(200).json({
message: 'Delete success',
});
})
.catch((err) => {
res.status(500).json({
message: err,
});
});
});
module.exports = useRouter;
프론트 웹팩 환경 구축
CRA를 사용하면 간편하게 프로젝트를 생성 할 수 있지만 웹팩과 여러 라이브러리와 플러그인들이 어떻게 동작해서 빌드를 하는지 알려면 직접 구축하고 설정해봐야 한다고 느꼈다. 물론 웹팩 설정하는데에 하루종일 걸린다는 말이 와닿는 날이였다.
웹팩 설정은 생활코딩, 김정환님, 제로초님 영상과 글들을 보면서 직접 하나하나씩 설정했다.
기본설정, 입력, 출력, 플러그인, 모듈, 웹팩용 서버 등 어떤 코드가 어떤 역할을 하는지 찾아 보면서 옆에 주석처리도 해놨다.
메인 입력 파일은 root폴더의 client.js 로 설정했고 출력은 root의 dist폴더에 빌드된다.
개인적으로 너무 좋은 설정은 resolve의 alias설정! 다른 파일에서 import할때 경로의 ../../../ 이런 꼴을 안봐도 된다.
그리고 프록시 설정을 통해 API호출을 위해 제작한 웹 서버의 주소도 설정할 수 있었다.
npm run build // 배포파일과 분석보고서를 dist 폴더에 생성한다.
npm run dev // 웹팩용 서버를 켜 프론트환경의 사이트를 접속할 수 있다.
그렇게 만든 내 webpack.config.js 파일
// const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const RefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
// const webpack = require('webpack');
// const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
// process.env.NODE_ENV = 'production'; // 배포모드
// const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
name: 'front', // 설정 이름
mode: 'development', // 배포모드 : production
devtool: 'inline-source-map',
// mode: isDevelopment ? 'development' : 'production',
// devtool: !isDevelopment ? 'hidden-source-map' : 'eval',
resolve: {
extensions: ['.js', '.jsx'], // 옆의 확장자가 있는지 확인
alias: {
'@components': path.resolve(__dirname, './src/components/'),
'@layouts': path.resolve(__dirname, './src/layouts/'),
'@pages': path.resolve(__dirname, './src/pages/'),
'@hooks': path.resolve(__dirname, './src/hooks/'),
'@utils': path.resolve(__dirname, './src/utils/'),
},
},
/* 입력 */
entry: {
app: ['./client'], // 확장자 검색기능을 추가해 확장자 작성하지 않아도 된다.
},
/* 출력 */
output: {
path: path.join(__dirname, 'dist'), // 현재 경로에 있는 'dist'라는 폴더
filename: '[name].js',
publicPath: '/dist',
},
/* 플러그인 */
plugins: [
new RefreshWebpackPlugin(),
new HtmlWebpackPlugin({ template: './index.html' }),
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({ filename: './common.css' }),
// isDevelopment && [
// new webpack.HotModuleReplacementPlugin(),
// new ReactRefreshWebpackPlugin(),
// new BundleAnalyzerPlugin({ analyzerMode: 'server', openAnalyzer: true }),
// ],
// !isDevelopment && [
// new webpack.LoaderOptionsPlugin({ minimize: true }),
// new BundleAnalyzerPlugin({ analyzerMode: 'static' }),
// ],
],
/* 웹팩 개발용 서버 */
devServer: {
devMiddleware: { publicPath: '/dist/' },
port: 3090,
hot: true,
static: { directory: path.resolve(__dirname) },
proxy: {
'/api/': {
target: 'http://localhost:3095',
changeOrigin: true,
},
},
}, // dev server 설정
/* 모듈 */
module: {
rules: [
{
test: /\.jsx?$/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['last 2 chrome versions'], // 지원되는 브라우저 설정, browserlist
},
debug: true, // 디버깅 옵션
},
],
'@babel/preset-react',
],
plugins: [
'react-refresh/babel', // 핫 리로딩
],
},
},
exclude: path.join(__dirname, 'node_modules'),
},
{
/* css */
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
exclude: /node_modules/,
},
{
/* file-loader */
test: [/\.png?$/, /\.jpg?$/],
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]',
},
},
},
],
},
};
오늘 설치한 라이브러리다.
- react
- sass(emotion이나 styled-components 고민 중)
- @trivago/prettier-plugin-sort-imports
- eslint(react, prettier 연결용)
점점 계획에 맞게 필요한 라이브러리들을 늘려나가고 서버와 통신하는법도 배우고 서버에 RestfulAPI도 만들어야한다.