본 과정에서는 React와 TypeScript 기반으로 낱말퍼즐 게임을 그려 보겠습니다.  첫 번째 포스트에서는 하려는 작업의 Concept와 환경설정에 대한 부분을 다루어 보겠습니다.

 

Concept

이번에 짬짬이 만들어서 가로세로 낱말퍼즐 게임 앱을 하나 출시했는데, 그 중에 Front-end 요소만 뽑아내어서 진행 과정을 설명해 보겠습니다. 어릴 때 스포츠 신문 등에서 많이 보던 가로세로 낱말퍼즐이라 모양은 익숙하실 것 같습니다.

 

실제로 동작하는 부분은 앱을 다운로드 받으셔서도 확인해 볼 수 있습니다. (안드로이드만 있습니다.)

https://play.google.com/store/apps/details?id=com.moa.puzzlekit 

 

퍼즐킷 - 낱말퍼즐 퀴즈 - Google Play 앱

가로세로 낱말퍼즐 게임입니다. 게임도 하고 상식도 쑥쑥! 키우세요

play.google.com

 

구현한 낱말 퍼즐 모양은 아래 그림과 같이 구성됩니다. (아래는 해당 애플리케이션 캡쳐 화면이며, 본 포스트에서는 퍼즐 모양에 대한 구현만 설명합니다.)

낱말퍼즐 게임

 

위 애플리케이션은 문제은행 방식이라 서버를 가지고 개발했지만 이번 포스트에서는 서버 데이터는 샘플 데이터를 만들어서 사용하겠습니다.

 

환경설정

코드 편집기는 VSCode를 사용하고, React TypeScript를 사용합니다. 패키지 관리도구는 yarn을 사용합니다. 개발에 필요한 환경구성은 아래 포스트를 참고하셔도 되고, 이번 과정에서도 동일한 구성으로 진행하겠습니다.

 

https://redballs.tistory.com/entry/React-%EA%B8%B0%EC%B4%88-%EB%AA%A9%EB%A1%9D-TypeScript-02-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1

 

React 기초 (목록 - TypeScript) 02 - 프로젝트 생성

본 포스트에서는 리액트와 타입스크립트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다. 이전 포스트에서 개발할 내용을 확인해 보았으니, 이제 개발에 필요한 환

redballs.tistory.com

 

워크스페이스 생성

먼저 개발에 사용할 워크스페이스 경로를 하나 만들고 패키지를 초기화 합니다. 필요한 파일들의 설치 등은 링크한 포스트를 참고하셔서 설치하시면 됩니다.

D:\workspace> mkdir makePuzzle
D:\workspace> cd makePuzzle
D:\workspace\makePuzzle> yarn init -y

 

연습으로 해보는 프로젝트이므로 패키지 관련 내용은 모두 초기값으로 설정하겠습니다. 위의 과정까지 완료하였으면 VSCode에서 프로젝트를 반입합니다.

 

모듈 설치

VSCode에서 터미널을 하나 열어서 필요한 모듈을 설치합니다. 편의를 위해 위 포스트와 동일한 버전으로 설치하겠습니다.

D:\workspace\makePuzzle> yarn add react@17.0.2
D:\workspace\makePuzzle> yarn add react-dom@17.0.2
D:\workspace\makePuzzle> yarn add --dev babel-core@6.26.3
D:\workspace\makePuzzle> yarn add --dev babel-loader@8.2.3
D:\workspace\makePuzzle> yarn add --dev babel-preset-react-app@10.0.1
D:\workspace\makePuzzle> yarn add --dev webpack@5.66.0
D:\workspace\makePuzzle> yarn add --dev webpack-dev-server@4.7.3
D:\workspace\makePuzzle> yarn add --dev webpack-cli@4.10.0
D:\workspace\makePuzzle> yarn add --dev typescript
D:\workspace\makePuzzle> yarn add --dev @types/react @types/react-dom

 

런타임에 필요한 모듈은 일반 의존성으로, 컴파일 타임에만 필요한 모듈들은 개발 의존성으로 설치하시면 됩니다.

 

Hello World

이제 실제로 프로그램을 작성해서 브라우저에 "Hello World"를 출력하는 부분까지 진행해 보겠습니다.

먼저 프로젝트 루트에 /src 라는 경로를 생성하고 아래와 같이 app.tsx 파일을 작성합니다.

const App = () => (
    <>Hello World</>
);

export default App;

 

동일한 경로에 해당 Component를 사용하는 main.tsx 파일도 아래와 같이 작성합니다.

import ReactDOM from 'react-dom';
import App from './app';

ReactDOM.render(<App />, document.getElementById('app'));

 

위 두 프로그램을 작성했으면 컴파일, 번들링을 진행하여 브라우저에서 한 번 실행해 보겠습니다. 일단은 빨간 줄이 떠도 무시합니다. 먼저 컴파일을 위해 프로젝트 루트에 tsconfig.json 파일을 아래와 같이 작성합니다.

{
    "compilerOptions": {
      "jsx": "react-jsx",
      "target": "es5",
      "module": "commonjs",
      "outDir": "./dist",
      "rootDir": "./src",
      "esModuleInterop": true,
      "forceConsistentCasingInFileNames": true,
      "strict": true,
      "skipLibCheck": true
    }
}

 

컴파일 할 대상은 ./src 하위에 있는 파일들로 설정하며 컴파일 완료한 파일은 ./dist 경로 하위에 배포하도록 설정합니다. 여기까지 완료하였으면 tsc 명령으로 컴파일 할 수 있습니다. 컴파일을 정상적으로 수행하고 나면 프로젝트에 ./dist 라는 경로가 생성되고 하위에 app.js, main.js 파일이 생성됩니다. 현재까지 프로젝트 구조는 아래와 같습니다.

프로젝트 구조

 

컴파일 한 js 파일들을 번들링하기 위해 webpack을 설정합니다. 가장 기본적인 설정만 넣어서 아래와 같이 webpack.config.js 파일을 프로젝트 루트에 작성합니다.

'use strict'
const path = require('path');

module.exports = {
    entry: {
        main: ['./dist/main.js']
    },
    output: {
        path: path.resolve(__dirname, './public/js'),
        filename: 'build.js'
    },
    module: {
        rules: [{
            test: /\.jsx?$/,
            use: {
                loader: 'babel-loader'
            }
        }]
    },
    devServer: {
        static: './public',
        host: 'localhost',
        port: 8080
    }
};

 

실행할 때 시작점은 컴파일 경로인 ./dist/main.js 로 설정하고 개발 서버의 루트는 ./public 하위 경로로 설정합니다. (아직은 만들지 않아서 해당 경로는 아직 없는게 맞습니다.) 포트는 무난하게 8080으로 합니다. 

output 항목에서는 번들링한 결과를 ./public/js/build.js 로 생성하도록 설정합니다.

 

번들링하고 해당 개발 서버를 start 할 수 있도록 package.json 파일에도 script를 추가합니다.

"scripts": {
    "start": "NODE_ENV=development webpack-dev-server",
    "build": "webpack"
}

 

이제 개발 서버의 루트 경로가 될 ./public 경로를 하나 생성하고 index.html을 작성합니다. index.html 파일에서는 번들링 결과인 build.js 파일을 import 합니다.

<!DOCTYPE html>
<html>
    <head>
        <title>낱말퍼즐</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    </head>
    <body>
        <div id="app"></div>
        <script type="text/javascript" src="./js/build.js"></script>
    </body>
</html>

 

Component를 렌더링할 영역으로는 app 라는 이름으로 영역을 하나 만들어 줍니다. 여기까지 완료했으면 이제 위에서 컴파일한 js 파일들을 번들링하여 build.js 로 변환합니다.

D:\workspace\makePuzzle> npm run build

 

명령을 실행하면 번들링 결과로 ./public/js/build.js 파일이 생성됩니다. 현재까지의 모두 진행한 프로젝트 파일 구조는 아래와 같습니다.

프로젝트 구조

 

이제 개발 서버를 시작하고 화면에 Hello World가 출력되는지 확인합니다.

D:\workspace\makePuzzle> yarn start

실행화면

 

이번 포스트에서는 개발 준비 작업으로 프로젝트 구조를 만들고 컴파일, 번들링 및 실행 환경까지 구성해 보았습니다. 다음 포스트에서는 이어서 낱말 퍼즐을 작성해 보겠습니다.

300x250

본 포스트는 React 애플리케이션 작성 시 로딩 다이얼로그, 프로그레스를 표시할 때 사용할 수 있는 오픈 소스인 React Spinners 사용에 대한 글입니다.

 

 

애플리케이션에서 서버 인터페이스나 모바일 Device의 기능을 사용하기 위해서 인터페이스 할 때, 아니면 내부적으로 무언가를 분석해야 해서 사용자와의 인터페이스를 잠시 막아두고 싶은 경우 Loading Dialog를 많이 사용합니다. jQuery를 사용할 때도 괜찮은 오픈 소스가 있어서 사용하거나, 아니면 그냥 Layer를 하나 뒤집어 씌운 다음에 괜찮은(?) 그림이나 애니메이션 하나를 올리는 방법을 사용했습니다.

 

React 애플리케이션에서 사용하기 괜찮은 오픈 소스가 하나 있어 소개합니다. 사실 코드에 대한 설명은 더 필요없을 정도로 깔끔하게 아래 사이트에 정리되어 있습니다.

https://mhnpd.github.io/react-loader-spinner/docs/intro

 

Getting Started | React Spinners

Installation

mhnpd.github.io

 

저 같은 경우는 Audio Component가 마음에 들어서, 해당 Component를 아래와 같이 사용해 보았습니다. 먼저 Loading Progress로 사용할 Component를 하나 생성합니다.

const Loading = () => {
    return (
        <div className="loadingContainer">
            <Audio
                height="60"
                width="60"
                color="#FFF"
                ariaLabel="audio-loading"
                wrapperStyle={{}}
                wrapperClass="wrapper-class"
                visible={true}
            />
        </div>
    )
};

 

Audio Component를 사용하고 백그라운드를 애플리케이션에 어울리는 형태로 커스터마이징 하기 위해서 loadingContainer라는 style을 사용하는 div로 한 번 감쌌습니다. 해당 CSS는 아래와 같습니다.

.loadingContainer {
    width:100%;
    height:100%;
    overflow:hidden;
    position:fixed;
    top:0;
    right:0;
    bottom:0;
    left:0;
    background-color:rgba(0,0,0,0.4);
    z-index:100;
    text-align:center;
}
.loadingContainer svg {
    position:absolute;
    top:calc(50% - 30px);
    left:calc(50% - 30px);
}

 

백그라운드를 화면 전체에 반투명으로 적용하고 Component의 위치를 선언했습니다. 이제 Loading Component를 사용할 Component 내부에 state를 선언합니다.

const [loading, setLoading] = useState<boolean>(false);

 

처음에는 Loading Component는 표시되지 않아야 하므로 초기 state는 false로 선언합니다. 이제 UI를 블록할 필요가 있는 부분에 Loading Component를 적용합니다. 아래 예시는 모바일 데이터를 통해 현재 위치를 가져오는 부분에 적용한 예시입니다.

setLoading(true);
navigator.geolocation.getCurrentPosition((pos) => {
    const slat: number = pos.coords.latitude;
    const slng: number = pos.coords.longitude;
	//TODO :: 구현
    setLoading(false);
}, () => {
    setLoading(false);
});

 

현재 위치를 수신하기 전에 loading state를 true로 변경한 후 작업이 정상으로 완료되거나, 실패로 완료되어도 loading state는 false로 변경합니다. 이제 마지막으로 UI Rendering code를 return 하는 부분에 state에 변경에 따라 Loading Component를 표시하거나 제거하는 로직을 추가합니다.

{
    loading ? <Loading /> : ''
}

 

위와 같이 작업하면 간단하게 Loading Dialog를 표시할 수 있습니다. 위의 예시를 사용한 화면은 아래와 같습니다.

 

300x250

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API 조회 Application을 재사용 하겠습니다.

 

 

React + Express + TypeScript 프로젝트 구성하기

 

이전 포스트에서는 express 서버에서 개발한 API에서 사용하는 데이터와 동일한 형태로 uiModule에도 /src/interface/apidata.interface.ts 파일에 데이터를 선언했습니다. 이번 포스트에서는 해당 데이터 타입을 사용하는 API 인터페이스 부분을 작성해 보겠습니다.

 

/src/component/listview.component.tsx 파일을 생성하고 API 데이터를 저장할 state를 하나 선언합니다. articles 상태에는 API 본문을 저장할 예정이므로 IRespData 형태로 선언합니다.

const ListView = () => {
    const [articles, setArticles] = useState<IRespData | null>(null);
}

export default ListView;

 

목록에 필요한 데이터는 로컬 서버에 Query 해야 하므로 fetch API를 사용해서 서버 API를 Query 하는 apiGet 함수를 하나 정의합니다.

const apiGet = async(type: string, param: string) => {
    const apiUrl: string = '/search/' + type + '/' + param;
    await fetch(apiUrl, {
        method: 'GET',
        headers: {
            'Content-Type': 'application/json'
        }
    })
    .then((resp: Response) => resp.json())
    .then((resp: IHttpResp) => {
        if (resp.code === '00') {
            setArticles(resp.data as IRespData);
            console.log(resp.data);
        } else {
            //handle error
        }
    });
};

 

Naver API는 서버 API에서 호출하므로 UI에서는 /search/{type}/{param} 형식으로 서버 API를 호출합니다.  도서 검색을 React 라는 파라미터로 검색을 하려면 /search/book/React 로 검색할 수 있습니다. apiGet 함수는 useEffect에서 호출하여 화면이 Loading 될 때 조회하도록 합니다.

useEffect(() => {
    apiGet('book', 'React');
}, []);

 

apiGet 함수를 호출할 때 React 관련 도서를 검색할 수 있도록 위와 같이 useEffect Hook을 작성합니다. 이번 포스트에서는 console.log로 resp.data가 올바르게 조회되는지 까지만 확인해 보겠습니다. 그러므로 UI는 별도로 작성하지 않고 <></> 만 return 하도록 합니다. 여기까지 작성한 listview.component.tsx 프로그램의 전체 소스코드는 아래와 같습니다.

import { useEffect, useState } from "react";
import { IHttpResp, IRespData } from "../interface/apidata.interface";

const ListView = () => {
    const [articles, setArticles] = useState<IRespData | null>(null);

    const apiGet = async(type: string, param: string) => {
        const apiUrl: string = '/search/' + type + '/' + param;
        await fetch(apiUrl, {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json'
            }
        })
        .then((resp: Response) => resp.json())
        .then((resp: IHttpResp) => {
            if (resp.code === '00') {
                setArticles(resp.data as IRespData);
                console.log(resp.data);
            } else {
                throw new Error('error');
            }
        });
    };

    useEffect(() => {
        apiGet('book', 'React');
    }, []);

    return (
        <></>
    );
};

export default ListView;

 

다음은 /src/app.tsx 프로그램을 아래와 같이 하나 작성하여 ListView 컴포넌트를 추가합니다.

import ListView from "./component/listview.component";

const App = () => {
    return (
        <ListView />
    )
};

export default App;

 

/src/main.tsx 소스코드에서는 <App /> 컴포넌트를 렌더링 하도록 아래와 같이 변경합니다.

import ReactDOM from 'react-dom';
import App from './app';

ReactDOM.render(<App />, document.getElementById('app'));

 

이제 서버와 인터페이스하여 수신한 데이터를 Logging 하는 부분까지는 준비를 마쳤습니다. 이제 타입스크립트를 컴파일하고 생성한 js 파일을 번들링하여 build.js를 생성합니다.

D:\workspace\expressServer\uiModule> tsc
D:\workspace\expressServer\uiModule> npm run build

 

/public/build.js 가 재생성 되었음을 확인하고 express 서버를 구동합니다.

D:\workspace\expressServer> yarn start

 

이제 브라우저로 http://localhost:8001 에 접속하면 아래와 같은 콘솔 로그를 확인할 수 있습니다.

인터페이스 결과 로그

 

아직 UI는 하나도 만들지 않았지만 이번 시리즈의 목적은 달성했습니다. 이번 시리즈의 목적은 아래 그림과 같았습니다.

서버 to 서버 인터페이스

 

NAVER API를 브라우저에서 직접 호출하지 않고 로컬에 express 서버를 하나 띄운 다음, express 서버에서 Naver API를 호출하여 중계하도록 Front-end, Back-end 프로그램을 작성해 보았습니다.

 

이후 UI를 만들어 보는 과정은 [React 기초 (List - TypeScript)] 시리즈의 "06 - ListView UI 구성" 포스트와 거의 동일한 내용이 될 것 같아서 작성하지 않겠습니다. 계속 진행해 보실 분들은 아래 링크를 참고하시면 됩니다.

React 기초 (목록 - TypeScript) 06 - ListView UI 구성 :: 즐거운인생 (tistory.com)

 

React 기초 (목록 - TypeScript) 06 - ListView UI 구성

본 포스트에서는 리액트와 타입스크립트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다. 저번 포스트에서는 Naver API를 호출하여 응답 데이터를 확인해 보고, 응답

redballs.tistory.com

 

300x250

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API 조회 Application을 재사용 하겠습니다.

 

 

React + Express + TypeScript 프로젝트 구성하기

 

이전 포스트에서는 expressServer 프로젝트 아래 uiModule 이라는 경로를 생성하고 해당 경로를 React 프로젝트로 구성한 후 소스코드를 컴파일 및 번들링해서 Node.js 서버에서 실행해보는 부분까지 진행해 보았습니다. 이번 포스트에서는 이전 포스트들에서 만든 서버 API에서 응답하는 데이터를 확인해 보고, Front-end에서 사용할 데이터 타입을 선언해 보겠습니다.

 

먼저 서버 API의 데이터 구조를 정의한 포스트는 아래 주소를 참고합니다.

React + Express + Typescript - 02. 데이터 타입 선언 :: 즐거운인생 (tistory.com)

 

React + Express + Typescript - 02. 데이터 타입 선언

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API

redballs.tistory.com

 

먼저 로컬 서버를 구동하고 http://localhost:8001/search/news/React 로 접근해서 데이터를 확인해 보겠습니다.

 

HTTP 응답

HTTP 응답은 아래와 같은 형태로 응답코드, 응답메시지, 응답데이터가 return 됩니다.

HTTP 응답

 

DATA 영역

HTTP 응답 중 데이터 영역은 아래와 같이 출력 건수(display), 시작건수(start), 전체건수(total), 검색유형(type), 검색 결과 데이터(items) 가 return 됩니다.

데이터부 응답

 

검색 결과 데이터 영역

검색 결과 데이터 영역은 두 가지 형태로 return 됩니다. type이 news일 경우에는 뉴스 데이터의 배열 형태로 return 되고, type이 book일 경우에는 도서 데이터의 배열 형태로 return 됩니다.

 

[type이 new일 경우]

뉴스 유형의 검색 결과

[type이 book일 경우]

도서 유형의 검색 결과

 

데이터 타입 선언

위 데이터를 분석해서 데이터 타입을 정의하면 되는데, 기본적으로는 서버 애플리케이션에서 정의한 데이터와 동일하게 정의하면 됩니다. 서버 애플리케이션의 정의를 참조하여 아래 경로에 프로그램을 작성합니다.

- ./uiModule/src/interface/apidata.interface.ts

 

먼저 HTTP 응답은 아래와 같이 작성합니다.

interface IHttpResp {
    code: string;
    message: string;
    data: IRespData | null
}

 

서버 요청에 대한 응답은 기본적으로 응답코드와 메시지, 조회한 데이터를 응답하도록 하고 조회한 데이터는 IRespData 형태로 아래와 같이 정의합니다. 오류일 경우 데이터가 없을 수도 있으니 null도 허용해줍니다.

interface IRespData {
    lastBuildDate: string;
    total: number;
    start: number;
    display: number;
    type: string;
    items: INewsData[] | IBookData[];
}

 

Naver API에서 주는 응답의 형태에 현재 데이터의 유형을 구분할 수 있는 type 속성을 추가합니다. 실제 조회한 데이터는 뉴스 데이터일 경우 INewsData의 배열 형태, 도서 데이터일 경우 IBookData의 배열 형태로 구성합니다. INewsData와 IBookData의 정의는 아래와 같습니다.

interface INewsData {
    title: string;
    originallink: string;
    link: string;
    description: string;
    pubDate: string;
}

interface IBookData {
    title: string;
    link: string;
    image: string;
    author: string;
    price: string;
    discount: string;
    publisher: string;
    pubdate: string;
    isbn: string;
    description: string;
}

 

다른 모듈에서 사용할 수 있도록 export를 추가합니다.

export {
    IHttpResp,
    IRespData,
    INewsData,
    IBookData
}

 

JSON 형태로 보면 아래와 같은 형태입니다.

{
    "code": "",
    "message": "",
    "data": {
        "start": 0,
        "total": 0,
        "display": 0,
        "lastBuildDate": "",
        "type": "",
        "items": [{
            "title": "",
            "description": "",
            "link": "",
            "originallink": "",
            "pubDate": ""
        }]
    }
}

 

데이터 타입 선언 부분은 서버와 클라이언트가 동일한 형태로 데이터를 참조해야 하므로 서버 개발자와 클라이언트 개발자가 별개로 있다면 협의해서 인터페이스 하는데 문제 없이 맞추어 가야합니다.

 

다음 포스트에서는 서버에서 제공한 API와 인터페이스하여 선언한 데이터 타입으로 바인드해서 확인해 보겠습니다.

300x250

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API 조회 Application을 재사용 하겠습니다.

 

 

React + Express + TypeScript 프로젝트 구성하기

 

저번 포스트까지 Node.js 서버를 express 기반으로 구성한 후 간단하게나마 서버 프로그램을 작성해서 Naver API를 호출하는 API를 작성하고 테스트 해보았습니다. 이번 포스트에서는 해당 API를 사용하여 UI를 구성할 React 프로젝트의 환경을 구성해 보겠습니다.

 

서버와 클라이언트 프로젝트는 별도의 Repository로 구성해도 되고, 하나의 통합 Repository로 구성해도 됩니다. 여기서는 별도의 프로젝트로 구성하지 않고 express 프로젝트 안에 uiModule이라는 경로를 생성하여 React 프로젝트를 구성해 보도록 하겠습니다.

 

먼저 expressServer 프로젝트의 루트 경로에 uiModule이라는 경로를 생성합니다.

D:\workspace\expressServer> mkdir uiModule
D:\workspace\expressServer> cd uiModule
D:\workspace\expressServer\uiModule>

 

yarn init 명령으로 package.json 파일을 생성합니다.

D:\workspace\expressServer\uiModule> yarn init -y

 

다음은 React 프로젝트에서 사용할 모듈을 설치합니다.  프로젝트에 사용할 모듈에 대한 설명은 아래 포스트를 참고합니다.

React 기초 (목록 - TypeScript) 02 - 프로젝트 생성 :: 즐거운인생 (tistory.com)

 

React 기초 (목록 - TypeScript) 02 - 프로젝트 생성

본 포스트에서는 리액트와 타입스크립트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다. 이전 포스트에서 개발할 내용을 확인해 보았으니, 이제 개발에 필요한 환

redballs.tistory.com

 

설치할 모듈은 아래와 같습니다.

D:\workspace\expressServer\uiModule> yarn add react@17.0.2
D:\workspace\expressServer\uiModule> yarn add react-dom@17.0.2
D:\workspace\expressServer\uiModule> yarn add --dev babel-core@6.26.3
D:\workspace\expressServer\uiModule> yarn add --dev babel-loader@8.2.3
D:\workspace\expressServer\uiModule> yarn add --dev babel-preset-react-app@10.0.1
D:\workspace\expressServer\uiModule> yarn add --dev webpack@5.66.0
D:\workspace\expressServer\uiModule> yarn add --dev webpack-cli@4.10.0
D:\workspace\expressServer\uiModule> yarn add --dev typescript
D:\workspace\expressServer\uiModule> yarn add --dev @types/react @types/react-dom
D:\workspace\expressServer\uiModule> yarn add moment@2.29.1

 

위 포스트에서 사용했던 webpack-dev-server는 이미 express로 구성한 서버가 있으므로 설치하지 않습니다.

 

/uiModule/src 경로를 생성하고, 아래와 같이 main.tsx 파일을 작성합니다

import ReactDOM from 'react-dom';

ReactDOM.render(<div>Hello World</div>, document.getElementById('app'));

 

타입스크립트를 사용하도록 tsx 파일로 생성했으므로 uiProject 하위에 tsconfig.json 파일을 아래와 같이 생성합니다.

{ 
    "compilerOptions": {
        "jsx": "react-jsx",
        "target": "es6",
        "module": "commonjs",
        "outDir": "./dist",
        "rootDir": "./src",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
    }
}

 

rootDir 이 ./src 이므로 uiModule/src 하위 파일들에 대해 컴파일을 수행하고 outDir 이 ./dist 이므로 uiModule/dist 하위에 컴파일 결과 파일을 생성합니다.

 

 

uiModule 의 package.json 파일에 build 스크립트를 아래와 같이 추가합니다.

{
  "name": "uiModule",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "moment": "2.29.1",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@types/react": "^18.0.15",
    "@types/react-dom": "^18.0.6",
    "babel-core": "6.26.3",
    "babel-loader": "8.2.3",
    "babel-preset-react-app": "10.0.1",
    "typescript": "^4.7.4",
    "webpack": "5.66.0",
    "webpack-cli": "4.10.0",
    "webpack-dev-server": "4.7.3"
  },
  "scripts": {
    "build": "webpack"
  }
}

 

여기까지 작업을 하면 uiModule 하위의 프로젝트 구조는 아래와 같습니다.

uiModule 프로젝트 구조

 

이제 tsc 명령으로 컴파일을 하면 아래 그림과 같이 dist 폴더가 생성되고 그 하위에 컴파일 결과가 생성됩니다.

D:\workspace\expressServer\uiModule> tsc

컴파일 결과 생성 확인

 

여기까지 잘 되었다면 이제 Node.js 서버에서 uiModule을 사용하기 위해 expressServer 프로젝트의 /public/js 하위로 컴파일 결과 파일들을 번들링하기 위해 아래와 같이 uiModule/webpack.config.js 파일을 생성합니다.

'use strict'
const path = require('path');

module.exports = {
    entry: {
        main: ['./dist/main.js']
    },
    output: {
        path: path.resolve(__dirname, '../public/js'),
        filename: 'build.js'
    },
    module: {
        rules: [{
            test: /\.jsx?$/,
            use: {
                loader: 'babel-loader'
            }
        }]
    }
};

 

애플리케이션의 시작점은 ./dist/main.js 로 컴파일 되므로 entry는 main.js 로 설정합니다. 번들링한 결과는 uiProject 외부에 있는 프로젝트 루트의 ./public/js로 전송해야 하므로 output은 위와 같이 설정하고 번들링한 결과는 build.js로 생성합니다.

D:\workspace\expressServer\uiProject> npm run build

 

npm run build 명령으로 번들링하면 아래 그림과 같이 결과가 생성됩니다.

번들링 결과 확인

 

번들링한 결과를 사용할 수 있도록 아래와 같이 index.html 파일을 수정합니다.

<!DOCTYPE html>
<html>
    <head>
        <title>Express Sample</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    </head>
    <body>
        <div id="app"></div>
        <script type="text/javascript" src="js/build.js"></script>
    </body>
</html>

 

이제 프로젝트 루트로 이동해서 yarn start 명령으로 서버를 실행시키고 화면에 Hello World가 표시되는지 확인합니다.

D:\workspace\expressServer> yarn start

 

 

실행 결과 확인

300x250

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API 조회 Application을 재사용 하겠습니다.

 

 

React + Express + TypeScript 프로젝트 구성하기

 

이전 포스트에서는 서버와 Naver API 서버 간, 클라이언트와 서버 간 인터페이스에 사용할 데이터 타입을 선언해 보았습니다. 이번 포스트에서는 Controller와 Service를 만들어서 실제로 인터페이스를 수행해 보겠습니다.

 

먼저 서버에서 Naver API와 통신을 위해서 request 모듈을 설치하겠습니다. 이전 포스트에서 React로 개발할 때는 Fetch API를 사용했지만 Node.js에는 Fetch API가 구현되어 있지 않아, node-fetch 모듈을 설치하면 유사하게 사용할 수 있지만 좀 더 많이 사용하는 request 모듈을 설치해서 사용해 보겠습니다.

D:\workspace\expressServer> yarn add request

 

request 모듈의 기본적인 사용법은 아래와 같습니다. 우리가 사용할 Naver API는 Get 방식으로 호출할 것이므로 request.get 메소드로 알아 보겠습니다.

const request = require('request');

request.get({
    headers: {},
    url: '',
    qs: {},
    json: true
}, function(error, response, body) {
});

 

request.get에 전달하는 파라미터 중 headers에는 HTTP Header 정보를 설정합니다. Content-Type과 Naver의 인증을 위한 인증 정보를 전달합니다. 아래 코드를 참고하여 각자가 발급 받은 인증 키를 입력합니다.

headers: {
    'Content-Type': 'application/json',
    'X-Naver-Client-Id': CLIENT_ID,
    'X-Naver-Client-Secret': CLIENT_SECRET
}

 

url은 파라미터를 제외한 부분을 입력합니다. 일단 뉴스 API로 간주하고 작성합니다.

uri: 'https://openapi.naver.com/v1/search/news

 

qs에는 해당 API를 Get 방식으로 호출할 때 전달할 QueryString 파라미터를 세팅합니다. 일단 검색에 정도만 세팅합니다.

qs: {
    query: "코로나"
}

 

우리는 API 요청에 대한 응답을 json으로 parsing하여 전달 받으면 되므로 json: true 옵션도 추가합니다.

 

Callback은 3개의 파라미터를 전달 받습니다. error, response, body를 전달 받는데 일단 우리는 오류 처리에 대한 부분은 body가 있는지 없는지를 보고 판단하겠습니다. body가 있을 경우에는 body에서 데이터를 추출하여 응답할 것이고, 없으면 오류 코드를 세팅하여 응답하도록 합니다.

const httpResp: HttpResp = new HttpResp();
const respData: RespData = new RespData(type);

if (body) {
    respData.setStart(body.start);
    respData.setDisplay(body.display);
    respData.setTotal(body.total);
    respData.setItems(body.items);
        
    httpResp.setCode('00');
    httpResp.setMessage('Success');
    httpResp.setData(respData);
} else {
    httpResp.setCode('99');
    httpResp.setMessage('Failed');
    httpResp.setData(null);
}

 

해당 조회하는 메소드는 비동기 처리를 위해 async 처리하여 Promise를 응답하도록 구성합니다. Service 프로그램은 ./src/naverapi/naverapi.service.ts 로 생성하고 아래와 같이 작성합니다.

import { HttpResp, RespData } from "../entity/httpresp.entity";
import { INaverApiResp } from "../interface/apidata.interface";

const NaverApiService = () => {
    const request = require('request');

    const search = async (type: string, param: string): Promise<HttpResp> => {

        return new Promise<HttpResp>((resolve, reject) => {
            const CLIENT_ID: string = '';
            const CLIENT_SECRET: string = '';
    
            const apiUrl: string = 'https://openapi.naver.com/v1/search/' + type;
            
            request.get({
                headers: {
                    'Content-Type': 'application/json',
                    'X-Naver-Client-Id': CLIENT_ID,
                    'X-Naver-Client-Secret': CLIENT_SECRET
                },
                uri: apiUrl,
                qs: {
                    query: param
                },
                json: true
            }, (error: object, response: object, body: INaverApiResp) => {
                const httpResp: HttpResp = new HttpResp();
                const respData: RespData = new RespData(type);

                if (body) {
                    respData.setStart(body.start);
                    respData.setDisplay(body.display);
                    respData.setTotal(body.total);
                    respData.setItems(body.items);
        
                    httpResp.setCode('00');
                    httpResp.setMessage('Success');
                    httpResp.setData(respData);
                } else {
                    httpResp.setCode('99');
                    httpResp.setMessage('Failed');
                    httpResp.setData(null);
                }

                resolve(httpResp);
            });
        });
    }

    return {
        search
    }
};

export default NaverApiService;

 

위에서 설명한 내용들로 구성되어 있습니다. apiUrl을 구성할 때는 뉴스와 도서를 하나의 메소드로 사용하기 위해 /search/ 하위에 타입을 추가하는 구조로 작성합니다. 다음은 Controller를 작성해 보겠습니다.

 

 

Controller 프로그램은 ./src/naverapi/naverapi.controller.ts 로 생성하고 아래와 같이 작성합니다.

import express, { Request, Response } from "express";
import { HttpResp } from "../entity/httpresp.entity";
import NaverApiService from "./naverapi.service";

const service = NaverApiService();
const NaverApiController = express.Router();

NaverApiController.get('/:type/:keyword', async (req: Request, res: Response) => {
    const httpResp: HttpResp = await service.search(req.params.type, req.params.keyword);
    res.send(httpResp);
});

export default NaverApiController;

 

express.Router()로 Router를 선언한 후 get 리스너를 추가합니다. URL은 아래와 같이 구성합니다.

http://localhost:8001/search/news/코로나

라는 URL로 접근할 경우 해당 요청에서 "news"와 "코로나"를 사용해서 Naver API에는 아래와 같이 요청하게 됩니다.

https://openapi.naver.com/v1/search/news?query=코로나

 

도서 API를 사용할 경우에도 위와 같은 규칙으로 동일하게 변환합니다.

http://localhost:8001/search/book/코로나

라는 URL로 접근할 경우 해당 요청에서 "book"과 "코로나"를 사용해서 Naver API에는 아래와 같이 요청합니다.

https://openapi.naver.com/v1/search/book?query=코로나

 

그리고 아까 생성한 Service class의 search 메소드를 호출한 결과를 응답합니다. search 메소드는 응답을 Promise<HttpResp> 타입으로 응답하므로 await 키워드를 사용하여 HttpResp 형태로 응답 받습니다.

 

이제 app.ts 프로그램에 app.use 를 사용해서 해당 Router를 앱에 추가합니다. 해당 Router는 /search 경로 하위에 매핑하겠습니다.

app.use('/search', NaverApiController);

 

해당 라인을 추가한 app.ts 의 전체 소스코드는 아래와 같습니다.

import express, { Request, Response } from "express";
import path from "path";
import NaverApiController from "./naverapi/naverapi.controller";

const app = express();

app.use(express.static(path.join(__dirname, '../public')));
app.get('/', (req: Request, res: Response) => {
    res.sendFile(path.join(__dirname, '../public/index.html'));
});
app.use('/search', NaverApiController);

app.listen('8001', () => {
    console.log('Server started');
});

  

이제 컴파일한 후 애플리케이션을 재시작 합니다.

D:\workspace\expressServer> tsc
D:\workspace\expressServer> yarn start

 

시작한 후 설정한 경로로 검색해서 결과를 한 번 확인해 보겠습니다.

먼저 news API를 "코로나"라는 키워드로 검색해서 결과를 확인해 보겠습니다.

 

[http://localhost:8001/search/new/코로나]

뉴스 API 확인

 

그 다음은 book API를 "코로나"라는 키워드로 검색해서 결과를 확인해 보겠습니다.

 

[http://localhost:8001/search/book/코로나]

도서 API 확인

 

클라이언트 프로그램에서 직접 Naver API를 호출할 때와 달리  CORS 오류가 발생하지 않고 잘 조회됨을 확인할 수 있습니다.

300x250

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API 조회 Application을 재사용 하겠습니다.

 

 

React + Express + TypeScript 프로젝트 구성하기

 

이전 포스트에서는 TypeScript 컴파일 환경 및 프로젝트 실행 환경을 구성하고 컴파일한 ./dist/app.js 프로그램으로 Node.js 서버를 구동하는 부분까지 작업해 보았습니다. 이번 포스트에서는 Controller와 Serivce를 생성해서 브라우저에서 받은 요청을 Naver API로 전달하여 응답을 받은 후, 사용자의 브라우저로 전달하는 부분을 작성해 보겠습니다.

 

데이터 타입 선언

먼저 각 데이터의 타입을 선언해 보겠습니다. 데이터의 타입은 interface로 선언하며 ./src/interface/ 하위에 apidata.interface.ts 파일을 생성하여 작성합니다. 이전 포스트에서 사용했던 Naver API의 데이터 종류는 아래와 같습니다.

 

인터페이스 응답

Naver API의 전체적인 응답 Layout은 아래와 같습니다.

{
    lastBuildDate: '',
    total: 0,
    start: 0,
    display: 0,
    items: []
}

 

뉴스 API 데이터

뉴스 API 요청에 대한 응답 Layout은 아래와 같습니다.

{
    title: '',
    originallink: '',
    link: '',
    description: '',
    pubDate: ''
}

 

도서 API 데이터

도서 API 요청에 대한 응답 Layout은 아래와 같습니다.

{
    title: '',
    link: '',
    image: '',
    author: '',
    price: '',
    discount: '',
    publisher: '',
    pubdate: '',
    isbn: '',
    description: ''
}

 

위에서 확인한 데이터를 interface로 ./src/interface/apidata.interface.ts 프로그램에 아래와 같이 선언합니다.

interface INaverApiResp {
    lastBuildDate: string;
    total: number;
    start: number;
    display: number;
    type: string;
    items: INewsData[] | IBookData[];
}

interface INewsData {
    title: string;
    originallink: string;
    link: string;
    description: string;
    pubDate: string;
}

interface IBookData {
    title: string;
    link: string;
    image: string;
    author: string;
    price: string;
    discount: string;
    publisher: string;
    pubdate: string;
    isbn: string;
    description: string;
}

export {
    INaverApiResp,
    INewsData,
    IBookData
}

 

해당 Layout은 Service class에서 참조해야 하므로 export하여 다른 class에서 참조할 수 있도록 합니다. Naver API 데이터에 대한 부분은 아래 포스트에서 예제를 확인할 수 있습니다.

React 기초 (목록 만들기) 05 - ListView UI 구성 :: 즐거운인생 (tistory.com)

 

React 기초 (목록 만들기) 05 - ListView UI 구성

본 포스트에서는 리액트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다. 저번 포스트에서 Naver News API를 호출해서 아래 형태로 결과를 받아보는 부분까지 진행해 보

redballs.tistory.com

 

Class 선언

추가로 두 가지 유형을 더 선언해 보겠습니다.  현재 작성하는 것은 서버 프로그램이므로 클라이언트의 HTTP 요청에 응답할 표준 Layout이 필요합니다. 간단하게 응답코드, 메시지, 데이터 본문으로만 구성해서 Class를 생성해 보겠습니다. 실제 사용 시에는 new 키워드로 인스턴스를 생성하여 사용하겠습니다.

 

interface와의 차이점은 interface는 Naver API에서 받아온 데이터의 응답에 대한 유형을 선언하고 타입 확인만 필요한 형태에 사용하지만 직접 데이터를 생성하는 부분은 class를 선언하고 인스턴스를 생성해서 사용합니다.

 

./src/entity/httpresp.entity.ts 파일을 생성하고 먼저 HTTP 요청에 대한 응답 Layout으로 사용할 HttpResp class를 아래와 같이 생성합니다.

class HttpResp {
    private code: string;
    private message: string;
    private data: RespData | null = null;

    constructor (code: string = '00', message: string = 'Success') {
        this.code = code;
        this.message = message;
    }

    public setCode = (code: string): void => {
        this.code = code;
    }

    public setMessage = (message: string): void => {
        this.message = message;
    }

    public setData = (data: RespData | null): void => {
        this.data = data;
    }
}

 

속성은 3개를 정의했습니다. 응답코드(code), 응답메시지(message), 응답데이터(data) 크게 3가지 정보를 사용하여 응답합니다. constructor는 new 키워드로 인스턴스를 생성할 때 사용하는 생성자입니다. code와 message를 파라미터로 받으며  입력하지 않을 경우 '00', 'Success'로 각각 세팅합니다.

 

응답데이터인 data는 RespData라는 타입으로 선언합니다. RespData라는 타입은 아직 생성하지 않았으니 아직은 오류가 발생합니다. 그리고 응답데이터는 초기에는 값이 없으므로 null도 허용해 주고, 초기값은 null로 세팅합니다. 아래와 같이 | (pipeline)으로 연결하면 여러 유형을 허용할 수 있습니다.

const data: RespData | null = null;

 

그리고 class에서 사용하는 속성들에 대해서는 setter만 선언 했습니다. 나중에 getter가 필요하게 되면 추가적으로 선언해도 되고, 서버에서 데이터를 수신 받아 응답만 할 목적이므로 아직까지는 getter는 필요가 없어 보입니다.

 

그럼 다음으로 응답데이터에 사용하는 RespData class를 아래와 같이 작성합니다. 별도의 파일로 생성하지 않고 동일한 파일에 작성합니다.

class RespData {
    private total: number = 0;
    private start: number = 0;
    private display: number = 0;
    private type: string = '';
    private items: INewsData[] | IBookData[] | null = null;

    constructor (type: string) {
        this.type = type;
    }

    public setTotal = (total: number): void => {
        this.total = total;
    }

    public setStart = (start: number): void => {
        this.start = start;
    }

    public setDisplay = (display: number): void => {
        this.display = display;
    }

    public setType = (type: string): void => {
        this.type = type;
    }

    public setItems = (items: INewsData[] | IBookData[]): void => {
        this.items = items;
    }
}

 

Naver API에서 넘겨주는 total (전체건수), start (시작 인덱스), display (표시 건수)는 숫자 형태로, 초기값을 0으로 선언합니다. 그리고 items는 Naver API에서 넘겨주는 뉴스나 도서 목록 데이터입니다. INewsData, IBookData의 배열이나 초기값을 위해 null 타입을 허용합니다.

 

그리고 type이라는 속성을 string 타입으로 생성합니다. 현재 조회한 목록이 뉴스 데이터인지, 목록 데이터인지를 구분하기 위한 값입니다. RespData class 역시 HttpResp와 동일한 사유로 getter는 작성하지 않았습니다. 다음 포스트 등에서 필요하게 되면 추가하겠습니다.

 

이제 필요한 데이터 타입은 모두 작성해 보았습니다. 다음 포스트에서는 실제 사용자의 요청을 받는 역할을 하는 Controller와 Naver API와 인터페이스를 수행하고 데이터를 만드는 Service를 작성하여 인터페이스를 진행해 보겠습니다.

 

300x250

본 포스트에서는 Front-end는 리액트, Back-end는 Express 기반의 Node.js를 사용하여 하나의 프로젝트로 간단한 Application을 구성해 보겠습니다. 구성할 Application은 이전 포스트에서 진행했던 네이버 API 조회 Application을 재사용 하겠습니다.

 

React + Express + Typescript 프로젝트 구성하기

 

이전 포스트에서 진행했던 Naver API 조회 프로젝트는 연습은 할 수 있는 아키텍처이나 실제로는 사용할 수 없는 아키텍처 입니다. 브라우저에 있는 CORS 정책을 사용자가 해제해주지 않으면 실행이 되지 않는 구조이기 때문입니다. 하이브리드 앱으로 애플리케이션을 구성한다면 사용이 가능하지만 웹 페이지로는 사용이 불가능합니다.

 

그래서 Naver API를 조회하는 부분은 서버로 로직을 옮겨 서버 to 서버 인터페이스로 변경하고 화면에서는 동일한 Domain(IP)에 있는 서버 인터페이스를 사용하여  CORS 정책을 해제하지 않고도 사용할 수 있는 아키텍처로 변경하겠습니다.

 

서버 to 서버 인터페이스

 

위 그림과 같은 구성은 다른 서버와의 인터페이스 주체가 서버이므로, 서버 간 통신에는 브라우저의 CORS 정책이 영향을 주지 못합니다. 이렇게 로컬 서버에서 Naver API 서버와 통신하여 데이터를 받아온 후 동일한 Domain(IP)에 있는 화면에서 해당 정보를 사용해서 검색 기능을 구현해 보겠습니다.

 

먼저 아래 포스트를 참고하여 Express 기반의 Node.js 개발 환경을 구성합니다.

https://redballs.tistory.com/entry/Nodejs-Express-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95?category=569060 

 

Node.js Express 환경 설정

본 포스트는 Express 기반의 Node.js 애플리케이션 서버를 구성하고 static 경로를 설정하는 방법을 설명합니다. 서론 Node.js 서버 애플리케이션을 개발할 때 주로 사용하는 Framework는 Express와 Nest 정도

redballs.tistory.com

 

포스트를 따라서 구성하고 프로젝트를 구동해보면 "Hello World"를 확인할 수 있습니다. 하지만 우리는 TypeScript를 사용하기로 했으니 필요한 모듈을 설치하고 프로젝트 구조를 살짝 변경해 보겠습니다.

 

 

TypeScript 설치

개발 의존성으로 node와 express 라이브러리의 타입을 설치합니다.

D:\workspace\expressServer> yarn add --dev @types/node
D:\workspace\expressServer> yarn add --dev @types/express

 

app.ts 로 변경

./src/app.js 프로그램을 ./src/app.ts 로 변경하고 아래와 같이 작성합니다. 기본적인 내용은 변경이 없고 필요한 부분에 타입만 추가합니다.

import express, { Request, Response } from "express";
import path from "path";

const app = express();

app.use(express.static(path.join(__dirname, '../public')));
app.get('/', (req: Request, res: Response) => {
    res.sendFile(path.join(__dirname, '../public/index.html'));
});

app.listen('8001', () => {
    console.log('Server started');
});

 

타입 스크립트 컴파일 옵션 설정

프로젝트 루트에 tsconfig.json 파일을 아래와 같이 작성합니다.

{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "outDir": "./dist",
        "rootDir": "./src",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true
    }
}

 

사용하는 컴파일 옵션에 대한 설명은 제 블로그의 아래 글을 참고해주세요.

https://redballs.tistory.com/entry/React-%EA%B8%B0%EC%B4%88-%EB%AA%A9%EB%A1%9D-TypeScript-03-Hello-World?category=566801 

 

React 기초 (목록 - TypeScript) 03 - 컴파일

본 포스트에서는 리액트와 타입스크립트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다. 저번 포스트에서는 프로젝트 경로를 생성하고 개발에 필요한 모듈들을 설

redballs.tistory.com

 

컴파일 옵션까지 작성했으면 tsc 명령으로 컴파일을 진행합니다. 컴파일한 결과 파일은 ./dist 하위에 생성됩니다.

D:\workspace\expressServer> tsc

 

실행 확인

현재는 start 스크립트를 ./src/app.js로 설정해 두었는데 이제는 컴파일한 ./dist/app.js 파일을 사용해야 하므로 package.json 파일의 start 스크립트를 아래와 같이 변경합니다.

"scripts": {
  "start": "node ./dist/app.js"
}

 

변경 후 yarn start 명령어로 로컬 서버를 기동하여 브라우저에서 "Hello World"가 잘 출력되는지 확인합니다. 현재까지 진행한 프로젝트 폴더 및 파일 구조는 아래와 같습니다.

프로젝트 구조

 

 

300x250

본 포스트에서는 리액트와 타입스크립트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다.

 

 

 

이번 과정에서 작성한 전체 소스코드는 중간중간 포스트에서 표시했습니다.

하지만 혹시 잘 안되시는 분들이 있으면 아래 Github에서 다운로드 해서 확인해 보시면 됩니다.

 

https://github.com/lgcjh0s/searchNaverApiTs

 

GitHub - lgcjh0s/searchNaverApiTs

Contribute to lgcjh0s/searchNaverApiTs development by creating an account on GitHub.

github.com

 

Checkout 받는 방법은 아래 포스트를 참고해주세요.

https://redballs.tistory.com/entry/Github-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-VSCode%EB%A1%9C-Checkout-%ED%95%98%EA%B8%B0?category=568554 

 

Github 프로젝트 VSCode로 Checkout 하기

본 포스트는 협업을 위해 Github에 업로드 되어 있는 프로젝트를 비주얼 스튜디오 코드로 체크아웃 하는 방법을 설명합니다. 1. 체크아웃 대상 프로젝트 다른 포스트에서 진행했던 searchNaverApiTs 프

redballs.tistory.com

 

300x250
본 포스트에서는 리액트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다.
 
 

 

이번 과정에서 작성한 전체 소스코드는 중간중간 포스트에서 표시했습니다.

하지만 혹시 잘 안되시는 분들이 있으면 아래 Github에서 다운로드 해서 확인해 보시면 됩니다.

 

https://github.com/lgcjh0s/searchNaverApi.git

 

GitHub - lgcjh0s/searchNaverApi

Contribute to lgcjh0s/searchNaverApi development by creating an account on GitHub.

github.com

 

체크아웃 받는 방법은 아래 포스트를 참고합니다.

https://redballs.tistory.com/entry/Github-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-VSCode%EB%A1%9C-Checkout-%ED%95%98%EA%B8%B0?category=568554 

 

Github 프로젝트 VSCode로 Checkout 하기

본 포스트는 협업을 위해 Github에 업로드 되어 있는 프로젝트를 비주얼 스튜디오 코드로 체크아웃 하는 방법을 설명합니다. 1. 체크아웃 대상 프로젝트 다른 포스트에서 진행했던 searchNaverApiTs 프

redballs.tistory.com

 

300x250

+ Recent posts