본문 바로가기

웹_프론트_백엔드/프론트엔드

2021.04.04

1. 다른 주소로 이동시키기
 : import {Route, Link} from 'react-router-dom';
   <Link to="주소">content</Link>

   [파라미터] /profile/apple
   [쿼리] /profile?name=apple

   * 쿼리를 문자열로 받아 처리 가능하지만 객체 형태로 변환하려면 qs 라이브러리를 사용
     └ yarn add qs

 

   * 서브라우트 : 라우트 내부의 라우트를 만드는 것을 의미한다.

 

** app.jsx

import React from 'react'
import {Route, Link} from 'react-router-dom';
import Home from './home';
import About from './about';
import Profiles from './profiles';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to='/'>홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필 목록</Link>
        </li>
      </ul>
      <hr/>
      <div>
        <Route path="/" exact={true} component={Home}/>
        <Route path="/about" component={About}/>
        <Route path="/profiles" component={Profiles}/>
      </div>
    </div>
  )
}

export default App;

 

** profile.jsx

import React from 'react';

const profileData = {
    apple: {
        name: '김사과',
        description: '학생, 착해요'
    },
    banana: {
        name: '반하나',
        description: '회사원, 잘자요'
    }
};

const Profile = ({match}) => {
    const {username} = match.params;
    const profile = profileData[username];

    if(!profile) {
        return <div>존재하지 않는 프로필입니다.</div>
    }
    return (
        <div>
            <h2>
                {username}({profile.name})
            </h2>
            <p>{profile.description}</p>
        </div>
    );
}

export default Profile;

 

** about.jsx

import React from 'react';
import qs from 'qs';

const About = ({location}) => {

    // props 전달되는 location 객체에 있는 search 값에서 데이터를 읽을 수 있음
    // (location 안에는 현재 앱이 갖고 있는 주소에 대한 정보)
    const query = qs.parse(location.search, {
        ignoreQueryPrefix: true
    });
    const detail = query.detail === 'true';

    return (
        <div>
            <h1>소개</h1>
            <p>리액트 라우터를 공부하고 있어요.</p>
            {detail && <p>추가적인 내용이 보일거예요</p>}
        </div>
    );
};

export default About;

 

** profiles.jsx

import React from 'react';
import {Link, Route} from 'react-router-dom';
import Profile from './profile';

const Profiles = () => {
    return (
        <div>
            <h2>유저 목록</h2>
            <ul>
                <li>
                    <Link to="/profiles/apple">apple</Link>
                </li>
                <li>
                    <Link to="/profiles/banana">banana</Link>
                </li>
            </ul>

            <Route path="/profiles" exact render={() => <div>유저를 선택하세요</div>}/>
            <Route path="/profiles/:username" component={Profile}/>
        </div>
    )
}

export default Profiles;

 

 

2. history 객체

 : 컴포넌트 내에 구현하는 메소드에서 라우터에 직접 접근을 할 수 있게 한다.
   뒤로가기, 특정 경로로 이동, 이탈방지 등 구현할 수 있다.

 

** historySample.jsx

import React, {useEffect} from 'react';

function HistorySample({history}) {
    const goBack = () => {
        history.goBack();
    }

    const goHome = () => {
        history.push('/');
    }

    useEffect(() => {
        console.log(history);
        const unblock = history.block('정말 떠나실건가요?');
        return () => {
            unblock();
        }
    }, [history]);

    return (
        <div>
            <button onClick={goBack}>뒤로가기</button>
            <button onClick={goHome}>홈으로</button>
        </div>
    )
}

export default HistorySample;

 

** app.jsx

import React from 'react'
import {Route, Link} from 'react-router-dom';
import Home from './home';
import About from './about';
import Profiles from './profiles';
import HistorySample from './historySample';

const App = () => {
  return (
    <div>
      <ul>
        <li>
          <Link to='/'>홈</Link>
        </li>
        <li>
          <Link to="/about">소개</Link>
        </li>
        <li>
          <Link to="/profiles">프로필 목록</Link>
        </li>
        <li>
          <Link to="/history">history 예제</Link>
        </li>
      </ul>
      <hr/>
      <div>
        <Route path="/" exact={true} component={Home}/>
        <Route path="/about" component={About}/>
        <Route path="/profiles" component={Profiles}/>
        <Route path="/history" component={HistorySample}/>
      </div>
    </div>
  )
}

export default App;

 

 

3. Koa
   app.use((ctx, next) => {
       ...
   })

** ctx(Context) : 웹 요청과 응답에 대한 정보를 가지고 있는 객체
** next : 현재 처리 중인 미들웨어의 다음 미들웨어를 호출하는 함수

 

** /backend/index.js

const Koa = require('koa');
const Router = require('koa-router');
const bodyParser = require('koa-bodyparser');
const api = require('./api');

const app = new Koa();
const router = new Router();

router.use('/api', api.routes());      // api 라우트 적용

app.use((ctx, next) => {
    console.log(ctx.url);
    console.log(1);
    next();
});

app.use((ctx, next) => {
    console.log(2);
    next();
});

app.use(ctx => {
    ctx.body = 'hello Koa!';
});

app.listen(4000, () => {
    console.log('4000번 포트로 서버 동작 중 ...');
});

 

** /backend/api/index.js

const Router = require('koa-router');
const posts = require('./posts');

const api = new Router();

api.use('/posts', posts.routes());


module.exports = api;

 

** /backend/api/posts/indexjs

const Router = require('koa-router');
const postsCtrl = require('./posts.ctrl');

const posts = new Router();

posts.get('/', postsCtrl.list);
posts.post('/', postsCtrl.write);
posts.get('/:id', postsCtrl.read);
posts.delete('/:id', postsCtrl.remove);
posts.put('/:id', postsCtrl.replace);

module.exports = posts;

 

** /backend/api/posts/posts.ctrl.js

let postId = 1;     // id의 초기값이다.

// posts 배열 초기 데이터
const posts = [
    {
        id: 1,
        title: '제목',
        body: '내용'
    }
];

/*  포스트 목록 조회
    GET /api/posts
*/
exports.list = ctx => {
    ctx.body = posts;
}

/*  포스트 작성
    POST /api/posts
*/
exports.write = ctx => {
    const {title, body} = ctx.request.body;
    postId += 1;
    const post = {id: postId, title, body};
    posts.push(post);
    ctx.body = post;
}

/*  특정 포스트 조회
    GET /api/posts/:id
*/
exports.read = ctx => {
    const {id} = ctx.params;
    // 주어진 id 값으로 포스트를 찾는다.
    // 파라미터로 받아온 값은 문자열 형식이므로 숫자로 변환하거나 비교할 id 값을 문자열로 변경해야 한다.
    const post = posts.find(p => p.id.toString() === id);

    if(!post) {
        ctx.status = 404;
        ctx.body = {
            message: '포스트가 존재하지 않습니다.'
        };
        return;
    }
    ctx.body = post;
}

/*  특정 포스트 제거
    DELETE /api/posts/:id
*/
exports.remove = ctx => {
    const {id} = ctx.params;
    const index = posts.findIndex(p => p.id.toString() === id);

    if(index === -1) {
        ctx.status = 404;
        ctx.body = {
            message: '포스트가 존재하지 않습니다.'
        };
        return;
    }
    posts.slice(index, 1);
    ctx.status = 204;       // 내용이 존재하지 않음
}

/*  특정 포스트 수정
    PUT /api/posts/:id
*/
exports.replace = ctx => {
    const {id} = ctx.params;
    const index = posts.findIndex(p => p.id.toString() === id);

    if(index === -1) {
        ctx.status = 404;
        ctx.body = {
            message: '포스트가 존재하지 않습니다.'
        };
        return;
    }
    post[index] = {
        id,
        ...ctx.request.body
    };
    ctx.body = posts[index];
}

 

 

4. .env 환경변수 파일 생성

   yarn add mongoose dotenv

 

   .env 파일
   PORT=4000
   MONGO_URI=mongodb://localhost:27017/blog

 

** /backend/.env

PORT=4000
MONGO_URI=mongodb://localhost:27017/blog

 

** /backend/index.js, 기존 index.js는 main.js로 이름 변경

require = require('esm')(module/*, options*/);
module.exports = require('./main.js');

 

** /backend/main.js

require('dotenv').config();

import Koa from 'koa';
import Router from 'koa-router';
import bodyParser from 'koa-bodyparser';
import mongoose from 'mongoose';

import api from './api';

const {PORT, MONGO_URI} = process.env;

mongoose.connect(MONGO_URI, {useNewUrlParser:true, useFindAndModify: false})
    .then(() => {
        console.log('mongodb 연결 성공!');
    })
    .catch(e => {
        console.error(e);
    });

const app = new Koa();
const router = new Router();

router.use('/api', api.routes());      // api 라우트 적용

app.use(bodyParser());
app.use(router.routes()).use(router.allowedMethods());

const port = PORT || 4000;

app.listen(port, () => {
    console.log('4000번 포트로 서버 동작 중 ...');
});

 

** /backend/models/post.js

import mongoose from 'mongoose';

const {Schema} = mongoose;

const PostSchema = new Schema({
    title: String,
    body: String,
    tags: [String],
    publishedDate: {
        type: Date,
        default: Date.now
    }
});

const Post = mongoose.model('Post', PostSchema);


export default Post;

 

** /backend/api/index.js

import Router from 'koa-router';
import posts from './posts';

const api = new Router();

api.use('/posts', posts.routes());


export default api;

 

** /backend/api/posts/index.js

import Router from 'koa-router';
import * as postsCtrl from './posts.ctrl';

const posts = new Router();

posts.get('/', postsCtrl.list);
posts.post('/', postsCtrl.write);

// /api/posts/:id
const post = new Router();
post.get('/', postsCtrl.read);
post.delete('/', postsCtrl.remove);
post.patch('/', postsCtrl.update);

posts.use('/:id', postsCtrl.checkObjectId, post.routes());


export default posts;

 

** /backend/api/posts/posts.ctrl.js

import Post from '../../models/post';
import mongoose from 'mongoose';
import Joi from '@hapi/Joi';

const {ObjectId} = mongoose.Types;

export const checkObjectId = (ctx, next) => {
    const {id} = ctx.params;
    if(!ObjectId.isValid(id)){
        ctx.status = 400;
        return;
    }
    return next();
}


let postId = 1;     // id의 초기값이다.

// posts 배열 초기 데이터
const posts = [
    {
        id: 1,
        title: '제목',
        body: '내용'
    }
];

/*  포스트 목록 조회
    GET /api/posts
    GET localhost:4000/api/posts
*/
export const list = async ctx => {

    const page = parseInt(ctx.query.page || '1', 10);

    if(page < 1) {
        ctx.status = 400;
        return;
    }

    try {
        const posts = await Post.find().sort({_id:-1})
        .limit(10)
        .skip((page - 1) * 10)
        .lean()
        .exec();
        const postCount = await Post.countDocuments().exec();
        ctx.set('Last-Page', Math.ceil(postCount / 10));
        ctx.body = posts.map(post => ({
            ...post,
            body:
            post.body.length < 200 ? post.body : `${post.body.slice(0, 200)}}...`
        }))
    } catch(e) {
        ctx.throw(500, e);
    }
}

/*  포스트 작성
    POST /api/posts
    POST localhost:4000/api/posts -> JSON
*/
export const write = async ctx => {

    const schema = Joi.object().keys({
        title: Joi.string().required(),
        body: Joi.string().required(),
        tags: Joi.array().items(Joi.string()).required()
    })

    const result = schema.validate(ctx.request.body);
    if(result.error) {
        ctx.status = 400;
        ctx.body = result.error;
        return;
    }

    const {title, body, tags} = ctx.request.body;
    const post = new Post({
        title,
        body,
        tags
    })

    try {
        await post.save();
        ctx.body = post;
    } catch(e) {
        ctx.throw(500, e);
    }
}

/*  특정 포스트 조회
    GET /api/posts/:id
    GET localhost:4000/api/posts/60ac946c095365392c1bcd6f
*/
export const read = async ctx => {
    const {id} = ctx.params;
    // 주어진 id 값으로 포스트를 찾는다.
    // 파라미터로 받아온 값은 문자열 형식이므로 숫자로 변환하거나 비교할 id 값을 문자열로 변경해야 한다.
    try {
        const post = await Post.findById(id).exec();

        if(!post) {
            ctx.status = 404;
            return;
        }
        ctx.body = post;
    } catch(e) {
        ctx.throw(500, e);
    }
}

/*  특정 포스트 제거
    DELETE /api/posts/:id
    DELETE localhost:4000/api/posts/60ac946c095365392c1bcd6f
*/
export const remove = async ctx => {
    const {id} = ctx.params;
    
    try {
        await Post.findByIdAndRemove(id).exec();
        ctx.status = 204;       // 성공은 했지만 응답한 데이터는 없음
    } catch(e) {
        ctx.throw(500, e);
    }
}

/*  특정 포스트 수정
    PATCH /api/posts/:id
    PATCH localhost:4000/api/posts/60ac946c095365392c1bcd6f
*/
export const update = async ctx => {
    const {id} = ctx.params;

    // write에서 사용한 schema, required()가 없음
    const schema = Joi.object().keys({
        title: Joi.string(),
        body: Joi.string(),
        tags: Joi.array().items(Joi.string())
    }) 

    const result = schema.validate(ctx.request.body);
    if(result.error) {
        ctx.status = 400;
        ctx.body = result.error;
        return;
    }

    try {
        const post = await Post.findByIdAndUpdate(id, ctx.request.body, {
            // new가 true일때, 업데이트 내용을 반환
            // new가 false일때, 업데이트 전 데이터를 반환
            new: true
        }).exec();

        if(!post) {
            ctx.status = 404;
            return;
        }
        ctx.body = post;
    } catch(e) {
        ctx.throw(500, e);
    }
} 

 

'웹_프론트_백엔드 > 프론트엔드' 카테고리의 다른 글

2021.04.07(보충)  (0) 2021.05.25
2021.04.03  (0) 2021.05.18
2021.03.28  (0) 2021.05.16
2021.03.27  (0) 2021.05.14
2021.03.21  (0) 2021.05.12