[DSC] Node.js + React를 통한 웹플랫폼 제작기 - 4주차

600g (Kim Dong Geun)·2020년 6월 1일
1
post-custom-banner

4주차 강의 리스트

  • Promise/then + Async Await이란?
  • Node.js 환경 구성
  • Mongoose와 ODM이란?
  • Mongoose와 Node 연결하기
  • Bitly 서비스 데이터 모델 규정하기
  • 3 Layers Architecture 설명하기
  • Business Logic 작성하기 (Random String을 이용한)
    • (시간 남으면 Base 62 인코딩을 사용한)
  • Controller 제작하기
  • CORS 설정하기
  • ReactRouter 설정하기
  • Axios로 연동하기
  • 첫번째 웹 플랫폼 완성!!

Promise/Then 과 Async Await이란?

프론트앤드 작업을 마치고, 이제 서버와 본격적으로 연동을 해볼 꺼에요.
그러기 앞서 먼저, 자바스크립트 문법 하나를 배우고 가보겠습니다.

앞서 Callback함수를 사용해서 자바스크립트의 비동기 호출에 대한 응답을 정의한다고 했었습니다.
기억안나시는 분들을 위해서 다시 한번 정리해서 가자면

function printHello(){
  setTimeout(()=>{
    console.log("hello world");
  },1000);
}

console창에 hello world를 1초뒤에 띄워라 이 함수 기억 나시나요?
그리고 ()=>{} 내부에 정의된 것이 바로 1초뒤에 실행되는 Callback 함수에 대한 정의라고 했어요.

비동기 함수는 코드의 순서가 어디있든 실행되는 순서를 추적하기 어려운 특성을 가지고 있어요.

비동기 함수 내에서도 순서를 보장하기 위해서 콜백함수 안에 콜백함수를 작성해야 되는 경우가 발생해요!

그럼 코드 구조는 다음과 같이 될 것 이에요.

어떤가요 이해하기 쉬운가요??

음 위구조에 대해서 이해하고 만들었다고 일단 칩시다^^
음,, 근데 코딩은 혼자하는게 아니죠? 또 코딩은 유지보수를 꾸준히 해야하는 작업이에요.

그런데 이전에 내가 짠 코드들을 다 기억하거나 남의 짠 코드를 쉽게 이해할 수 있을까요?
더군다나 {{{{{}}}}}방식의 Callback hell 구조라면요?

그래서 자바스크립트에서는 이 Callback Hell 구조를 해결하기 위해 2가지 방안을 제시 해줬습니다.

첫번째는 Promise/Then, 두번 째는 Async Await 입니다.

그럼 Promise/Then에 대해서 설명 드리도록 할께요!

그럼 확 와닿게 직접 Callback 구조를 한번 짜볼까요?

function printText(){
    setTimeout(()=>{
      console.log("만나서");
      setTimeout(()=>{
        console.log("반가워요"); 	
        setTimeout(()=>{
            console.log("어떠신가요 다들");
        },1000);
      },1000);
    },1000);
}

Promise/Then 은 콜백 hell에 대해 다음과 같은 구조로 만들어주도록 제시해줬습니다

printText("만나서")
.then(()=>printText("반가워요"))
.then(()=>printText("어떠신가요 다들"));

만나서 뒤에 반가워요 가 실행되고 그 뒤에 어떠신가요 다들이 출력됩니다.
비동기임에도 코드의 순서가 보장되고 코드가 훨씬 깔끔해졌죠?

Promise/Then 실행할 함수에 then을 붙임으로써 순서를 깔끔하게 정의할 수 있어요.

그렇지만 Promise/Then을 사용하기 위해선 사용할 함수가 promise 구조를 가지고 있어야 해요
그럼 위에 정의된 printText가 어떻게 구현되어있는지 한번 보도록 하겠습니다.

function printText(text){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log(text);
            resolve();
        },1000);
    });
}

위 코드를 설명하기 위해선, promise 구조를 일단 다 빼볼께요.

function printText(text){
        setTimeout(()=>{
            console.log(text);
        },1000);
}

1초 뒤에 텍스트를 출력하라라는 함수 입니다.

그럼 그냥 Promise Then의 구조를 한번 적어볼께요

function printText(text){
  return new Promise((resolve,reject)=>{
    if(err) reject();
    resolve();
  });

reject()함수는 에러가 있을시 reject를 실행 시키라는 의미입니다.
그것이 아니라면 resolve를 실행시킨다는 의미를 가지고 있습니다.

이 resolve가 무엇을 의미하느냐, 바로 Then에서 정의되는 함수입니다.

그래서 1초뒤에 계속 printText(~~~) 를 실행을 하게 되는 겁니다.
이 reject는 무엇을 의미하냐!
비동기를 진행 하다보면 error가 발생할 수 있는데, 그러면 Then으로 빠지는 것이 아니라
Catch로 빠지게 됩니다.

위에선 생략을 했지만,
Promise/Then의 구조는 다음과 같은 구조를 지니게 됩니다.

promiseFunc()
.then(nextFunc)
.catch(error=>{});

function promiseFunc(){
  return new Promise((resolve,reject)=>{
  	if(error) reject();
    resolve();
  }}

다음은 Async Await입니다.

Async Await은 Promise Then의 구조도 귀찮고 복잡하다!

좀 더 코드를 코드답게 만들어보자 해서 나온 것입니다.

그래서 Promise Then의 함수를 그대로 씁니다.
즉 위의 promise then의 함수를 그대로 들고 와보겠습니다.

printText("만나서")
.then(()=>printText("반가워요"))
.then(()=>printText("어떠신가요 다들"));

이 것을 다음과 같이 변경하도록 합니다

await printText("만나서");
await printText("반가워요");
await printText("어떠신가요 다들");

훨씬 보기 깔끔해졌죠?

여기서 await은 printText를 다 실행할 때까지 기다려라 라는 의미입니다!

단 Async Task를 사용하기 위해선 2가지의 제약 조건이 붙어있습니다.

  • 상위 함수가 Async로 감싸져있을 것
  • 실행되는 함수가 Promise로 구현된 함수 일것

즉 printText의 함수가 Promise로 감싸져 있어야하고 실행되는 곳에서 async로 감싸져야 합니다.

음 코드를 보시면서 실행해보는게 더 이해하기 쉬울 겁니다!

async function printAllText(){
  await printText("만나서");
  await printText("반가워요");
  await printText("어떠신가요 다들");
}


function printText(text){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            console.log(text);
            resolve();
        },1000);
    });
}

즉 async로 함수를 선언하는 것은 => 아 이 함수는 비동기로 실행할거다
await을 만난다면 => 비동기를 진행하지만 await이 끝날때까지 기다려라!라는 의미가 됩니다!

reject에 대해서 어떻게 처리하냐구요?
try/catch로 묶으면 오류에 대해서도 충분히 대응할 수 있습니다.

async function printAllText(){
  try{
    await printText("만나서");
    await printText("반가워요");
    await printText("어떠신가요 다들");
  }catch(error){
    console.log(error);
  }
}

훨씬 코드가 깔끔해졌죠?
앞으로 비동기 코드를 사용할 때는 대부분 Async를 사용하거나, Promise를 사용할 것 같습니다!

async/await에 대해 이해하셨다면, 여러분은 자바스크립트 최소 중급자 이상^^

Node.js + Express.js 환경 구성

저번시간 프론트 까지 만들었죠?
자 그럼 다시 본격적으로 Bit.ly 서비스 만들기에 다시 들어가보도록 하겠습니다.

Node Express로 서버를 구성할꺼에요.
사실 이건 많이 해봤죠?

express bitly-server --view=ejs

를 구성합시다.

cd bitly-server 이후 npm install 혹은 yarn install

를 해주시면 기본적인 서버 세팅은 완료 했습니다.

Mongoose와 ODM이란?


음,, 이부분을 설명하자고 하니, 시간이 조금 초과할 것 같아서, 간단하게 설명만 하고 넘어갈게요.

DB는 RDBMS와 NOSQL 크게 두 종류로 나뉜다고 했죠?

여기서 RDBMS는 SQL이라는 질의어를 던져서 우리가 원하는 데이터를 입/출력을 합니다.

그럼 NOSQL은 SQL을 사용하지 않는다고 했죠?
그렇지만 원하는 데이터를 가져오기 위해서 명령어를 사용해서 데이터를 입/출력을 해야하긴 합니다.

그러면 결국 데이터베이스를 사용할려면 SQL이나, 다른 명령어를 배워야 하잖아요 ㅠㅠ

맞아요. 배워야 합니다 (백엔드라면 무조건으로다가)

그렇지만 이 SQL을 매번 적용하기 위해서는 시간적/코드적(?) 비용이 많이 듭니다.

그래서 SQL을 사용하지 말고, 각 언어(java면 java, js면 js)의 코드를 이용해서 DB를 이용하자 해서 나온 것이 바로 ORM(RDBMS), ODM(Nosql && Document중심) 입니다.

즉 Javascript의 ODM은 다음과 같이 이해 하시면 될 것 같습니다.

DB에 대한 관리를 Javascript 코드로 하겠다. (DB - ODM - Javascript)

ODM에 대한 설명과 ORM에 대한 설명은 기본적으로 비슷하니 링크 첨부하겠습니다^_^
한번 읽어보세요! => 보러가기

실제로 ORM과 ODM은 많은 종류가 존재 합니다.
Javascript에서는 크게 Sequelize와 Mongoose가 존재합니다
Sequelize는 Mysql, MariaDB와 같은 RDBMS를 매핑해주는 ORM입니다
그럼 Mongoose는?

Mongoose는 Javascript에서 MongoDB를 관리해주는 ODM 라이브러리입니다.

이름 부터 그렇게 생겼죠? 👀👀
그럼 저번에 세팅한 몽고 DB와 Mongoose를 한번 연동해보도록 하겠습니다.

Node.js 와 Mongoose 연동하기 🤝

자 그럼 아까 그 구성한 bitly-server로 한번 들어가봅시다.

그리고 Mongoose 라이브러리를 설치해봅시다.

npm install mongoose 혹은 yarn add mongoose

그리고 model이란 폴더를 하나 만들고 그안에 index.js를 하나 생성합니다.
그리고 다음과 같이 설정하겠습니다.

  • model/index.js
const mongoose = require("mongoose");

try {
  mongoose.connect(
    "mongodb+srv://여러분의 몽고디비 주소를 입력하세요!",
    { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true }
  );
}catch(error){
    console.log("DB Connection Error", error);
}

const db = mongoose.connection;
db.on('error',()=>console.log("mognoose error!"));
db.once('open',()=>{
    console.log("DB Connection Success!");
});

module.exports = db;

기본적인 세팅은 https://mongoosejs.com/docs/index.htmlhttps://mongoosejs.com/docs/connections.html 를 참조했습니다.

저기 mongodb+srv:// ~~ 로 시작하는 부분은
https://cloud.mongodb.com/ 여기에 접속하셔서 connect누르시면

를 볼수 있는데 여기서 <password>부분만 바꿔서 넣어주시면 됩니다.

💡 공식 Document를 읽는건 생활화 하세요. 제일 정확하고 빠르게 배울 수 있어요. 💡

그리고 App.js 에 돌아와서 다음 문장을 추가 시켜줍니다.

  • app.js
...
var logger = require('morgan');

const mongoose = require('./model'); // <= 이 부분을 추가 시킵니다.

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
...

그 이후 터미널로 다음 명령어를 입력합니다.

npm start 혹은 yarn start

위 그림과 같이 DB Connection Success!가 뜨면 성공입니다!

Bitly 데이터 모델 규정하기

Bitly는 긴 URL을 짧은 URL로 변경해주는 서비스라고 했습니다.

그러면 저희는 짧은 URL 을 입력했을때, 긴 URL로 매핑을 해주어야 하죠?

ShorURLLongURL
a1234http://www.naver.com
goodhttp://velog.io/@ehdrms2034

와 같은 디비 구조를 가지도록 해야 합니다.

그럼 저희는 Mongoose를 이용해서 정의 해보도록 하겠습니다.

MongoDB에서 이러한 데이터 구조를 정의한다는 것을 Schema를 정의한다고 합니다.

그럼 model 폴더 내에 url schema를 정의해보겠습니다.

  • /model/url.js
const mongoose = require("mongoose");

const {Schema} = mongoose;

const url = new Schema({
    shortUrl :{
        type : String,
        unique : true,
    },
    longUrl : {
        type : String,
        required : true,
    }
},{
    versionKey: false
});

module.exports = mongoose.model('url',url);

위 코드에 대한 설명을 간략히 하자면 url Schema는 앞으로 데이터를

{
  shortUrl : "String 값",
  longUrl: "String 값"
}

을 가지고 short Url은 중복을 허용하지 않겠다(unique : true) 라는 뜻입니다.

이제 모델 구성을 완료 했으니 비즈니스 로직을 한번 만들어 봅시다!.

3 Layers Architecture

코드를 깔끔하게 하기위해서 혹은 협업을 하기 위해서 코드에 대한 일종의 패턴 혹은 규칙을 만들어 놓은 것이 있습니다.

이를 디자인 패턴 이라고 하는데, 3Layers Architecture도 이 Design Pattern중에 하나입니다.

사실 기초 단계에서 비즈니스 로직을 분리하고, 계층별로 코드를 관리한다는 것이 어려울 수도 있기 때문에, 더 자세히 알아보고 싶은 분들을 위해서 링크를 첨부합니다

저한테도 도움이 많이 된 포스팅이었습니다 👍👍 링크 => 견고한 node.js 프로젝트 설계하기

저는 신입한테는 디자인 패턴을 잘 요구하지 않는다. 라고 들은적이 있지만,,
개인적인 생각으로는 잘 안와닿는 말인 것 같습니다.😅 저는 사이드 프로젝트나, 포트폴리오를 제시하기 위해서는 디자인패턴대로 설계하는 것이 좋은 점수를 받지 않을까합니다.

3-layers Architecture의 종류를 세분화 하자면
Spring : MVC
Android, IOS : MVC,MVP, MVVM
React : Container/Presenter Pattern, Redux Pattern
등 다양하게 쓰이고 있습니다.

디자인 패턴? 백엔드 개발자라면 최소한 MVC 구조라도 알아두자! 👍

그런겸 Controller(routes) - Service - Model로 분리 시켜 코드를 작성하겠습니다.

Business Logic 제작 - Random String을 이용한 방법

먼저 service 폴더를 만듭니다.
그리고 UrlService.js 를 하나 생성합니다

다음과 같이 코드를 작성합니다

  • service/UrlService.js
const UrlModel = require("../model/url");

class UrlService {
  constructor() {}
  
  //short url을 min~max 글자로 랜덤하게 생성해주는 함수입니다.
  createRandomString(min,max) {
    var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
    var string_length = Math.floor(Math.random() * (max - min + 1)) + min;
    var randomstring = "";

    for (var i = 0; i < string_length; i++) {
      var rnum = Math.floor(Math.random() * chars.length);
      randomstring += chars.substring(rnum, rnum + 1);
    }
    return randomstring;
  }

  /*
    longUrl을 받아 short URL로 DB로 전환해줍니다.
    randomShortUrl을 만들고 DB에 중복된 shortURl이 존재하지 않으면,
    DB에 shortUrl 과 longUrl을 저장합니다.
  */
  async createUrl(longUrl) {
    let randomShortUrl;
    while(true){
      randomShortUrl = this.createRandomString(4,8);
      const data = await this.findOneByShortUrl(randomShortUrl);
      console.log(data);
      if(data == null) break;
    }

    const newUrl = new UrlModel({
      longUrl : longUrl,
      shortUrl : randomShortUrl
    });

    await newUrl.save();
    return newUrl;
  }

  //longUrl값으로 UrlModel을 불러오는 함수 입니다.
  async findShortUrlByLongUrl(longUrl){
    return await UrlModel.findOne({longUrl : longUrl});
  }

  //shortUrl값으로 UrlModel을 불러오는 함수입니다.
  async findOneByShortUrl(shortUrl){
    return await UrlModel.findOne({shortUrl: shortUrl});
  }

}
module.exports = UrlService;

LongUrl에서 ShortUrl로의 비즈니스 로직을 만들었습니다.

Contoller 제작하기

자 이제 비즈니스 로직을 구성했으니, 클라이언트의 요청을 받고 응답을 내줄 Controller(router)를 만들어봅시다.

routes 폴더내에 index.js 파일 내용을 다지우고 다음과 같이 입력합시다!

  • routes/index.js
const express = require("express");
const router = express.Router();

const UrlService = require("../service/UrlService");

const urlService = new UrlService();

function generateBody(response, message, data) {
  return { response, message, data };
}

router.post("/", async (req, res) => {
  const { longUrl } = req.body;
  try {
    if (longUrl === undefined)
      throw new Error("request body값이 조회되지 않음");
    const newUrl = await urlService.createUrl(longUrl);

    return res.json(
      generateBody("success", "short Url 요청 성공", {
        longUrl: newUrl.longUrl,
        shortUrl: newUrl.shortUrl,
      })
    );
  } catch (error) {
    return res.json(
      generateBody("error", "shortUrl을 요청 실패", error.message)
    );
  }
});

//주소의 /:shortUrl 값을 아래에 const {shortUrl} 값으로 쓰겠다 라는 뜻
router.get("/:shortUrl", async (req, res) => {
  const { shortUrl } = req.params;
  try {
    if (shortUrl === undefined)
      throw new Error("request path 값이 존재하지 않습니다.");

    const data = await urlService.findOneByShortUrl(shortUrl);

    if (data === null || data === undefined)
      throw new Error("short Url(" + shortUrl + ")값이 정확하지 않습니다.");

    return res.json(
      generateBody(
        "success",
        "url을 성공적으로 들고왔습니다.",
        data.longUrl
      )
    );
  } catch (error) {
    return res.json(
      generateBody("error", "요청에 실패했습니다.", error.message)
    );
  }
});

module.exports = router;

자 저 같은 경우는 포스트맨을 통해서 한번 위 로직이 정상적으로 작동하는지 한번 테스트 해보겠습니다.

다음과 같이 POST 요청에 localhost:3000/ 으로
longUrl : "www.naver.com" 이라는 정보를 줘 보겠습니다.

라는 요청을 주면 다음과 같은 응답 결과를 받으실 수 있을겁니다.

그럼 서버를 켠상태에서 http://localhost:3000/4xPKOIK (shortUrl값에 맞게 변경)를 입력해보겠습니다.

네이버로 연결되시나요^^?

기본적인 로직 끝 프론트와 연결만 남았어요!

나는 DB를 효율적으로 쓰고싶다하면 DB에 Long Url이들어있는지 검사해보고
있다면 shortUrl을 새로 생성하는 것 대신 기존의 shortUrl값을 리턴해주면 되겠죠^^?

아무튼 넘어가겠습니다. bitly는 사용자에 따라서 같은 url이라도 다른 short url을 리턴해주고 있는 것 같으니..!

CORS 설정하기

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)란?

한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다

쉽게 설명하자면 현재 저희는 DB - Server - Client의 구조를 띄고 있습니다.
Server와 Client가 같은 위치가 아닌 서로 다른 위치에 존재하기 때문에, 정보 교환을 막겠다는 뜻입니다.

해결하는 방법은 2가지가 있습니다.
1. Client를 서버내에 위치시킨다.
2. Server의 CORS 정책을 변경한다.

1번 2번 모두 쉬운 방법이지만 2번을 하도록 하겠습니다.
(1번이 보안상 안전하지만 확장가능성이 떨어집니다.)

npm install cors 혹은 yarn add cors

로 npm module을 설치할께요

그리고 App.js에 다음 2줄을 추가시킵니다.

const mongoose = require('./model');
const cors = require('cors')(); //<=요거 추가

var indexRouter = require('./routes/index');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(cors); //<=요거 추가2

React Router를 세팅

그럼 저번주차로 다시 가보도록 하겠습니다!
bitly-client 가 있는 폴더로요!

저번에 환경 세팅을 다 마쳐서 React Router 라이브러리는 기본적으로 있을겁니다.

자 다음과 같이 해봅시다.

  • src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { BrowserRouter, Route } from "react-router-dom";
import * as serviceWorker from "./serviceWorker";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Route path="/" exact component={App} />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

그리고 Component 폴더를 만들어 UrlComponent.js 를 하나 만들고 다음과 같이 작성합시다.

  • src/Component/UrlComponent.js
import React from "react";

const UrlComponent = ({match}) => {
    return(
        <div>
            {match.params.shortUrl}
        </div>
    )
}
export default UrlComponent;

match는 localhost:3000/:shortUrl 의 값을 가져오기 위한 것입니다.
밑에 마저 설명하도록 하겠습니다.

그리고 다시 index.js로 가서 코드 두줄을 추가해줍시다.

import UrlComponent from "./Compoent/UrlComponent"; //요거

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Route path="/" exact={true} component={App} />
      <Route path="/:shortUrl" component={UrlComponent}></Route> //요거
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

그리고 localhost:3000/과 localhost:3000/hellowrold를 한번 입력해봅시다.
서로 다른 결과가 출력되는 걸 볼 수 있나요?

코드를 설명하자면 경로가 '/' (기본경로) 인경우는 App.js를 표시하겠다는 겁니다
대신 / 뒤에 부가 정보가 붙었다면 shortUrl을 Url Component에 보내겠다는 겁니다.

그럼 Url Component에서는 match.params.shortUrl로 위정보를 가져올 수 있는 것이구요!

이제 Axios를 통해서 코드 연결을 해보도록 하겠습니다.
그전에 node 포트와 react포트가 겹치니까 node 포트를 바꿔주겠습니다

  • bitly-server/bin/www
var port = normalizePort(process.env.PORT || '3000');

이부분을 다음과 같이 수정해 줍시다.

var port = normalizePort(process.env.PORT || '5050');

넵 이제 연결 합시다 연결!

Axios로 클라이언트와 서버 연결하기

프론트 작업을 마저 해줍시다.

다음과 같이 변경합니다.

  • app.js
function showConfirm(longUrl) {
  confirm({
    title: "변경된 URL",
    content: "기존의 url:"+longUrl+" , \n 변경된 url : ",
    onOk() {},
    onCancel() {},
  });
}

function App() {
  const [longUrl, setLongUrl] = useState("");

  return (
    <Container>
      <Title>DSC Short URL</Title>
      <RowSpan>
        <CustomInput
          size={"large"}
          onChange={(e) => setLongUrl(e.currentTarget.value)}
        />
        <ConvertButton
          onClick={() => {
            showConfirm(longUrl,"");
          }}
        >
          Convert
        </ConvertButton>
      </RowSpan>
    </Container>
  );
}

longUrl에 대한 State 값의 변경과 Text가 변경될때마다 LongUrl값이 변경되도록 설정했습니다.

그럼 input에 http://www.naver.com 을 누르고 Convert를 누르면 다음과 같이 뜨나요?

이제 Axios를 사용해봅시다.

  • app.js
function showConfirm(longUrl, shortUrl) {
  confirm({
    title: "변경된 URL",
    content: "기존의 url:" + longUrl + " , \n 변경된 url : " + shortUrl,
    onOk() {},
    onCancel() {},
  });
}

const EndPoint = "http://localhost:5050";
const MyPoint = "http://localhost:3000";

function App() {
  const [longUrl, setLongUrl] = useState("");

  const convertUrl = async (longUrl) => {
    try {
      const response = await Axios.post(EndPoint + "/", { longUrl: longUrl });
      const {data} = response;
      if (data.response === "error") throw new Error(data.message);
      const shortUrl = MyPoint+"/"+data.data.shortUrl;
      showConfirm(longUrl, shortUrl);
    } catch (error) {
      alert(error.message);
    }
  };

  return (
    <Container>
      <Title>DSC Short URL</Title>
      <RowSpan>
        <CustomInput
          size={"large"}
          value={longUrl}
          onChange={(e) => setLongUrl(e.currentTarget.value)}
        />
        <ConvertButton
          onClick={() => {
            convertUrl(longUrl);
          }}
        >
          Convert
        </ConvertButton>
      </RowSpan>
    </Container>
  );
}
export default App;
  • /Component/UrlComponent.js
import React, { useEffect } from "react";
import Axios from "axios";

const EndPoint = "http://localhost:5050";

const UrlComponent = ({match,history}) => {

    useEffect(()=>{
        async function init (){
            try{
                const {data} = await Axios.get(EndPoint+'/'+match.params.shortUrl);
                if(data.response === "error") throw new Error("error 발생");
                window.location.href=data.data;                
            }catch(error){
                alert("error 발생");
                history.push('/');
            }
        }
        init();
    },[]);

    return(
        <div>
        </div>
    )
}
export default UrlComponent;

끝!!

이제 한번 localhost:3000 에 들어가서 실험 해봅시다.

제대로 실행이 되시나요?

ROBO 3T를 실행해 DB 정보를 보시면

다음과 같이 뜬다면 성공입니다^^

끝!!

App.js와 Url Component 전체소스 남기고 갑니다

  • app.js
import React, { useState } from "react";
import { Input, Button, Modal } from "antd";
import "antd/dist/antd.css";
import styled from "styled-components";
import Axios from "axios";

const { confirm } = Modal;

const Container = styled.div`
  @import url("https://fonts.googleapis.com/css2?family=Lobster&display=swap");
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
  background: #468fe1;
`;

const Title = styled.div`
  margin-bottom: 40px;
  font-size: 3rem;
  color: white;
  font-family: "Lobster", cursive;
  text-shadow: 1px 1px 4px gray;
`;

const RowSpan = styled.div`
  display: flex;
  flex-direction: row;
  height: 40px;
  margin-bottom: 200px;
`;

const ConvertButton = styled(Button)`
  height: 100%;
  background: #f7a62d;
  color: white;
  border: none;
  border-radius: 5px;
  font-weight: bold;
  box-shadow: 1px 1px 4px gray;
`;

const CustomInput = styled(Input)`
  width: 600px;
  margin-right: 20px;
  border-radius: 5px;
  box-shadow: 1px 1px 4px gray;
`;

function showConfirm(longUrl, shortUrl) {
  confirm({
    title: "변경된 URL",
    content: "기존의 url:" + longUrl + " , 변경된 url : " + shortUrl,
    onOk() {},
    onCancel() {},
  });
}

const EndPoint = "http://localhost:5050";
const MyPoint = "http://localhost:3000";

function App() {
  const [longUrl, setLongUrl] = useState("");

  const convertUrl = async (longUrl) => {
    try {
      const response = await Axios.post(EndPoint + "/", { longUrl: longUrl });
      const {data} = response;
      if (data.response === "error") throw new Error(data.message);
      const shortUrl = MyPoint+"/"+data.data.shortUrl;
      showConfirm(longUrl, shortUrl);
    } catch (error) {
      alert(error.message);
    }
  };

  return (
    <Container>
      <Title>DSC Short URL</Title>
      <RowSpan>
        <CustomInput
          size={"large"}
          value={longUrl}
          onChange={(e) => setLongUrl(e.currentTarget.value)}
        />
        <ConvertButton
          onClick={() => {
            convertUrl(longUrl);
          }}
        >
          Convert
        </ConvertButton>
      </RowSpan>
    </Container>
  );
}
export default App;
  • Component/UrlComponent.js
import React, { useEffect } from "react";
import Axios from "axios";

const EndPoint = "http://localhost:5050";

const UrlComponent = ({match,history}) => {

    useEffect(()=>{
        async function init (){
            try{
                const {data} = await Axios.get(EndPoint+'/'+match.params.shortUrl);
                if(data.response === "error") throw new Error("error 발생");
                window.location.href=data.data;                
            }catch(error){
                alert("error 발생");
                history.push('/');
            }
        }
        init();
    },[]);

    return(
        <div>
        </div>
    )
}

export default UrlComponent;
profile
수동적인 과신과 행운이 아닌, 능동적인 노력과 치열함
post-custom-banner

0개의 댓글