스터디 포스트 >  웹 개발 실전 프로젝트

node 뿌시기(MEN stack)-1

조재홍 멘토
이로운 개발자가 되고 싶은 조재홍입니다.

웹 개발 실전 프로젝트 - 3주차

 
💡
Node JS, Express, MongoDB를 배우고 연동해보자
 

313. Node JS 개요

  • JavaScript는 설치하지 않는 대신 ECMAScript 사양을 갖추어야 한다.
  • 오랫동안 브라우저는 JavaScript를 작성하고 실행할 수 있는 유일한 장소였다. JavaScript가 웹 애플리케이션에 적합한 이유도 바로 이 때문.
  • Node는 브라우저 밖에서 작동되는 JavaScript의 실행 환경
  • 2009년에 Ryan Dahl이 Node.js를 만들었고, 2011-2012년 사이 엄청난 주목을 받기 시작했다.
  • 사람들이 개발한 애드온, 패키지, 도구 등 모듈의 수가 엄청나게 많다. ⇒ Node에 관련된 커뮤니티가 강력하다.
 

314. Node는 어디에 사용하는가?

  • 웹 서버 구축 즉, 서버측 로직을 써서 풀스택 애플리케이션을 만들 수 있다.
  • 지금 AJAX로 API를 요청해서 정보를 받고 브라우저에서 작업할 수 있지만, 내 API를 만들고 싶다면 어떻게? 내 DB를 만들어 거기에 웹 서버를 연결하고 풀 애플리케이션을 만들고 싶다면? ⇒ Node
  • Netflix, Uber, NASA도 Node를 사용한다.
  • Node의 유명한 프레임워크 Express 사용도 나중에 배울 예정
  • line tool(명령줄 도구)란 전통적 사용자 인터페이스가 없는 경우에, 명령줄에서 실행되는 애플리케이션을 말한다. 조만간 배울 NPM 등도 명령줄 도구에 해당된다.
  • 네이티브 앱도 Node로 만든다.
  • VS Code, Slack 등
 

316. Node REPL

  • Node REPL
    • Read, Evaluate, Print, Loop
  • 브라우저의 JavaScript 콘솔은 REPL이다.
  • .help 를 입력하면 사용할 수 있는 명령어를 제공한다.
  • Node에는 DOM API와 Window, Document가 없다.
  • Node에서 전역 객체는 global이다.
  • 브라우저에서 쓰는 기능이 없지만, 브라우저에 없는 기능도 많다.
    • 전부 내장 모듈이고, 이 모듈을 이용해 운영 체제와 파일과 폴더를 다룰 수 있다.
 

317. Node 파일 실행

  • JavaScript 파일에서 코드를 작성하고 Node에서 실행하기
node firstScript.js
 

318. 프로세스와 Argv

  • Express가 없는 Node
  • Node 버전 보기 node -v
  • process 는 Node에서 사용하는 객체로, 사용 중인 Node의 정보를 담고 있고, 전역 범위에 있다.
notion image
notion image

process.argv

  • 첫 번째 요소는 process.execPath
  • 두 번째 요소는 실행 중인 파일
notion image
  • 스크립트에서 세 번째 인수를 전달할 수 있다.
const args = process.argv.slice(2);
for (let arg of args) {
  console.log(`Hi there, ${arg}`);
}
greeter.js
notion image
 

319. 파일 시스템 모듈의 충돌 과정

  • Node에는 상당한 양의 내장 메서드와 다양한 특성이 있다.
  • fs(file system) 모듈
    • 가져와서 써야 한다. require
  • 파일이나 폴더를 삭제하는 메서드는 동기/비동기 두 가지 방식이 있다.
    • 동기 메서드는 작업이 끝날 때까지 모든 프로세스를 차단
const fs = require('fs');
// console.log(fs);

// 비동기 메서드
fs.mkdir('Dogs', { recursive: true }, (err) => {
 console.log('IN THE CALLBACK!')
 if (err) throw err;
})

// 동기 메서드
fs.mkdirSync('Cats');

console.log('I COME AFTER MKDIR IN THE FILE!');
  • 사이에 코드를 추가하기 전에 디렉토리 생성부터 해야 한다면 동기 메서드를 사용하는 것이 쉽다. mkdirSync
const fs = require('fs');
const folderName = process.argv[2] || 'Project';
// argv의 0번째 인수는 실행 가능한 경로, 1번째는 파일 경로
// argv의 2번째 인자는 사용자가 처음으로 작성한 인수
// 이를 변수로 저장하고 디폴트를 'Project'로 작성해 주었다.

fs.mkdirSync(folderName);
  • 파일 생성 메서드는 writeFilewriteFileSync 가 있다.
  • 파일에 데이터를 기록하거나 기존 파일을 대체한다.
const fs = require('fs');
const folderName = process.argv[2] || 'Project';

try {
  fs.mkdirSync(folderName);
  fs.writeFileSync(`${folderName}/index.html`);
  fs.writeFileSync(`${folderName}/styles.css`);
  fs.writeFileSync(`${folderName}/app.js`);
} catch (e) {
  console.log('SOMETHING WENT WRONG!!');
  console.log(e);
}
 

321. Module.exports 사용하기

빈 객체
빈 객체
  • app.js에서 math.js 파일을 불러오더라도, math.js 파일 안에서 명시적으로 특정 항목을 내보내라고 명령하지 않는 한 app.js에서는 아무것도 가져오지 않는다.
    • 자동으로 파일 내 모든 항목에 액세스할 수는 없다.
  • module.exports 는 기본 값으로 객체에 해당하는 특성이 있다.
    • 아무 것도 적지 않아서 빈 객체를 내보낸 것
  • 액세스 하려는 항목을 프로퍼티든 함수나 변수든 module.exports에 포함시켜줘야 한다.
const add = (x, y) => x + y;
const PI = 3.141592;
const square = x => x * x;

module.exports.add = add;
module.exports.PI = PI;
module.exports.square = square;
math.js
const { PI, square } = require('./math');

console.log(PI);
console.log(square(9));
app.js에서 가져오려는 모듈 구조 분해 가능 → 원하는 것만 불러올 수 있다.
const add = (x, y) => x + y;
const PI = 3.141592;
const square = x => x * x;

const math = {
	add: add,
	PI: PI,
	square: square
}

module.exports = math;
이런 식으로 다른 객체로 지정하는 것도 가능 (문자열 지정도 가능, 잘 쓰이지 X)
module.exports.add = (x, y) => x + y;
module.exports.PI = 3.141592;
module.exports.square = x => x * x;
처음부터 module.exports에 추가할 수도 있다.
  • module을 생략한 exports 단축어 구문으로 사용할 수도 있다.
 

322. 디렉터리의 필요성

const blue = require('./blue');
const janet = require('./janet');
const sadie = require('./sadie');

const allCats = [blue, janet, sadie];

module.exports = allCats;
shelter/index.js
const cats = require('./shelter');
console.log('REQUIRED AN ENTIRE DIRECTORY!', cats);
app.js
  • 디렉토리를 불러오기 하면, 디렉토리의 index.js가 내보내는 것을 가져오게 된다.
  • 라이브러리를 만들거나 라이브러리로 작업을 할 때도 index.js가 중요한 역할을 한다.
 

323. NPM 개요

  • 패키지는 다른사람이 쓴 코드로, 설치해서 프로젝트에 통합시킬 수 있게 쓰여진 코드다.
  • NPM은 그런 노드 패키지를 위한 표준화된 저장소로, Node Package Manager를 뜻한다.
  • 노드 커뮤니티는 보통 NPM을 지칭한다.
  • Node는 NPM 명령줄 도구(command line tool)와 같이 제공되어서, 손쉽게 이 패키지들을 설치하고 관리할 수 있게 해준다.
 

324. 패키지 설치하기 - Jokes와 Rainbow

  • 패키지 설치해보기

npm install

  • 설치할 때는 정확한 패키지 이름을 적었는지 유의해야 한다.
npm i give-me-a-joke
  • 설치 후 node_modules 폴더를 보면 많은 항목들이 있는데, 설치한 패키지에 대한 dependencies 의존성에 해당된다.
  • 패키지를 불러오는 방법은 파일의 경로를 쓰지 않는다는 것이다. node_modules/give-me-a-joke/index ❌ 그냥 패키지의 이름을 참조하면 된다.
const jokes = require('give-me-a-joke'); // ⭕
console.dir(jokes);
console.dir(jokes)의 결과. 4개의 메서드가 있다고 나온다.
console.dir(jokes)의 결과. 4개의 메서드가 있다고 나온다.
npm i colors
const jokes = require('give-me-a-joke');
const colors = require('colors');

jokes.getRandomDadJoke((joke) => {
  console.log(joke.rainbow);
  console.log(joke.inverse);
})
notion image
 

325. 글로벌 패키지 추가하기

  • 패키지의 버전 관리를 package.json에서 한다.
  • 프로젝트 디렉토리 같은 하위 디렉토리가 아니라, 전체 계정 및 전체 사용자에 대해 전역 설치 하려면 -g 플래그를 추가하면 된다.
npm i -g cowsay
cowsay 안뇽
cowsay 안뇽

326. Package.json의 중요성

  • package.json은 모든 노드 앱에 들어가고, 특정 프로젝트나 패키지, 앱에 대한 메타 데이터 즉, 정보를 가지고 있다.
    • description, license, name, version, dependencies, ...
  • 보통 NPM 명령어를 통해 만들어 진다.
npm init
notion image
  • 여러 정보들을 묻고나면 package.json 파일이 생성된다.
notion image
npm i figlet
notion image
  • 설치하면 하단에 dependencies가 붙는다.
  • 이렇게 이 앱에서 사용하고 있는 모든 것들을 기록한다.
figlet과 colors 설치
const figlet = require('figlet');
const colors = require('colors');

figlet('Hello World!!', function (err, data) {
  if (err) {
    console.log('Something went wrong...');
    console.dir(err);
    return;
  }
  console.log(data.rainbow);
});
notion image
  • 유용한 이유 1) 착수 중인 작업에 대한 기록을 남길 수 있고 2) 다른 사람 또는 다른 기기에서 스스로에게 이 파일을 공유할 때 모든 디펜던시들을 한번에 다운로드할 수 있기 때문
 

327. 한 프로젝트에 대한 모든 종속 요소 설치하기

  • npm install을 실행하고 package.json 파일에 기반하여 디펜던시 설치
  • 더이상 쓰지 않게 된 디펜던시는 package.json에서 삭제하면 된다.

328. 언어 맞추기 프로젝트 도전

npm i franc langs
// const franc = require('franc');
// const langs = require('langs');
import { franc } from 'franc';
import langs from 'langs';
import colors from 'colors';

const input = process.argv[2];

const langCode = franc(input);
if (langCode === 'und') {
  console.log("SORRY, COULDN'T FIGURE OUT! TRY WITH MORE SAMPLE TEXT!")
}
const language = langs.where('3', langCode);

console.log(`Our best guess is: ${language.name}`.rainbow);
 

330. Express 개요

  • Express는 웹 개발을 위한 프레임워크로, Node를 사용하여 서버를 생성하고 실행할 수 있다.
  • 웹 애플리케이션 개발을 위해 다양한 메서드, 애드온, 플러그인을 제공한다. API 생성 등의 작업도 도와준다.
  • 요청을 받아들일 서버 구축을 돕고, 들어오는 요청(request)을 파싱하는데, HTTP 요청이 텍스트 정보이기 때문이다. (JavaScript 객체가 아님)
  • /about 이나 /contact 등과 같이 어떤 요청인지에 따라 실행되는 다양한 코드와 함수를 작성할 수 있다.
  • 응답(response)을 만드는 것도 도와준다. 상태 코드 설정, 헤더 등의 콘텐츠를 어떻게 응답할 지 설정
 
  • 프레임워크와 라이브러리는 다른 사람이 작성한 코드이고, NPM 같은 도구를 이용해 다운로드한다.
  • 단일 스크립트이거나 Axios를 쓴다면 HTTP 요청 라이브러리
  • 프레임워크의 목표와 목적에는 차이가 있는데, 제어와 제어의 역전이다. (control / inversion of control)
  • 라이브러리는 언제든지 코드에 결합할 수 있고, 어떻게 사용할지를 결정하는 메서드와 기능을 제공한다. 제어권을 우리가 갖는다.
  • 프레임워크는 구조를 제공하지만 제어를 역전시켜서 우리는 제어에 거의 관여하지 않는다. 프레임워크의 작동 원리에 따라 코드를 작성하기만 하면 된다. 왜냐면 풀 애플리케이션의 개발을 프레임워크가 돕기 때문이다.
  • 라이브러리는 그보다 작은 HTTP 요청이나 단일 사용 단일 목적의 역할만 한다.
  • 개발 속도와 프레임워크가 제공하는 기능을 얻는 대신, 코드를 작성하는 부분에서 유연성과 자유를 놓치게 된다.
  • 파일에 뭘 넣는지, 어느 폴더에 저장하는지도 제어하니 우리가 많은 제어권을 포기해야 한다. 하지만 배우는 단계를 넘어서면 빠른 앱 개발을 도와준다.
 

331. 우리의 첫 번째 Express 앱

FirstApp 폴더 생성 후 npm init -y (질문 생략)
익스프레스 설치 npm i express
  • 구버전 NPM에서 패키지를 package.json에 저장하려면 --save 를 입력해야 했다. (신버전에서는 넣을 필요 없음)
const express = require('express');
const app = express();
Express 애플리케이션

app.listen

서버가 요청을 받게 해보기
첫 번째 인수로 포트 번호, 두 번째 인수로 콜백 함수를 추가해서 앱이 요청을 받기 시작하거나 포트로 요청을 받으면 함수가 실행되게 할 수 있다.
app.listen(3000, () => {
  console.log('LISTENING ON PORT 3000!')
})
  • 로컬 호스트는 사용하는 로컬 기기를 의미한다.
notion image
  • Cannot GET / 은 서버는 있지만 응답을 받지 못했다는 뜻
  • Express 앱을 여러 개 실행해야 하면 포트 번호를 각각 다르게 지정하면 된다.

app.use

  • 서버에 요청이 들어오면 콜백을 실행시킨다.
  • 요청이 어디에서(/home이든 /contact든) 어떤 형태로(GET, POST, ...) 들어오든 상관 없이 요청(request)이라면 모두 작동한다.
const express = require('express');
const app = express();

app.use(() => {
  console.log('WE GOT A NEW REQUEST!!');
});

app.listen(3000, () => {
  console.log('LISTENING ON PORT 3000!');
});

332. 요청 및 응답 객체

  • 들어오는 모든 요청은 app.use 함수에서 자동으로 전달되는 두 개의 매개변수에 접근한다.
    • req 들어오는 요청을 의미하는 객체
    • res 응답을 의미하는 객체
💡
Express가 두 객체를 전달하면 요청 객체는 들어오는 요청인 HTTP 요청을 받아들이고, 응답 객체는 요청을 한 누군가에게 보내질 응답을 생성하는 데 쓰인다.
  • HTTP 요청은 JavaScript객체가 아니라 텍스트 정보다.
  • Express는 자동으로 HTTP 요청 정보를 파싱해 JavaScript 객체로 변환하고, app.use의 콜백 함수의 첫 번째 인수로 전달한다.
  • 응답 객체의 메서드 res.send는 HTTP 요청을 보낸다. 예를 들어, 자동으로 HTTP 응답 헤더 필드를 할당한다.
const express = require('express');
const app = express();

app.use((req, res) => {
  console.log('WE GOT A NEW REQUEST!!');
  res.send('<h1>This is my webpage!</h1>');
});

app.listen(8080, () => {
  console.log('LISTENING ON PORT 8080!');
});

333. Express 라우팅 기초

  • 각 요청에 맞는 다른 콘텐츠로 응답하기
  • 라우팅 - 요청과 요청된 경로를 가져와서 응답을 갖는 어떠한 코드에 맞추는 것

app.get

  • 일치하는 경로 이름콜백 함수를 인수로 받는다.
const express = require('express');
const app = express();

// app.use((req, res) => {
//   console.log('WE GOT A NEW REQUEST!!');
//   res.send('<h1>This is my webpage!</h1>');
// });

app.get('/cats', (req, res) => {
  // console.log('CAT REQUEST!');
  res.send('MEOW!!');
});

app.get('/dogs', (req, res) => {
  res.send('WOOF!!');
});

app.listen(8080, () => {
  console.log('LISTENING ON PORT 8080!');
});
  • app.use를 주석처리한 이유는 res.send로 응답을 보낼 때마다 하나의 요청으로 끝나기 때문이다.
    • 모든 요청에 응답한다.
    • 하나 이상의 응답을 얻는 HTTP 요청을 받을 수 없다.
 
  • 홈(루트) 라우트와 post 요청 추가하기
const express = require('express');
const app = express();

// app.use((req, res) => {
//   console.log('WE GOT A NEW REQUEST!!');
//   res.send('<h1>This is my webpage!</h1>');
// });

app.get('/', (req, res) => {
  res.send('This is the home page!')
})

app.post('/cats', (req, res) => {
  res.send('POST REQUEST TO /cats!!')
})

app.get('/cats', (req, res) => {
  // console.log('CAT REQUEST!');
  res.send('MEOW!!');
});

app.get('/dogs', (req, res) => {
  res.send('WOOF!!');
});

app.listen(8080, () => {
  console.log('LISTENING ON PORT 8080!');
});
 
  • 제네릭(generic) 응답을 만들어서 라우트와 일치하지 않을 때 응답할 내용을 작성할 수 있다. app.get('*', () => {})
  • 가장 끝에 위치해야 한다!
app.get('*', (req, res) => {
  res.send(`I don't know that path!`)
})
 

334. Express 경로 매개변수

req.params 경로 매개변수

💡
라우트의 콜론 : 뒤에 경로를 정의하는 문자열을 넣고, 키-값 쌍이 있는 req.params 를 사용하면 된다.
  • 라우트 제네릭 패턴 정의하기
  • subreddit 명에 따라 콘텐츠를 바꾸려면 req 객체를 통해 이름에 접근하면 된다.
app.get('/r/:subreddit', (req, res) => {
  console.log(req.params);
  res.send('THIS IS A SUBREDDIT!')
})
notion image
  • 정확히 매치되는 라우트가 아닌 경우, 경로 매개변수(params)나 경로 변수를 라우트나 패턴에 설정하는 기본 방법
app.get('/r/:subreddit', (req, res) => {
  const { subreddit } = req.params;
  res.send(`<h1>Browsing the ${subreddit}</h1>`);
});
  • 중첩 라우팅
app.get('/r/:subreddit/:postId', (req, res) => {
  const { subreddit, postId } = req.params;
  res.send(`<h1>Viewing Post ID: ${postId} on the ${subreddit} subreddit</h1>`);
});
 

335. 쿼리 문자열 작업하기

  • 쿼리란 URL의 일부로 물음표 ? 뒤에 위치
    • 쿼리스트링의 한 부분으로써 키-값 쌍으로 정보를 담을 수 있다.
    • 쿼리스트링을 파싱하고 Express 앱에서 접근하는 것은 중요하다.

req.query 쿼리스트링 파싱

app.get('/search', (req, res) => {
  console.log(req.query);
  res.send('HI!');
});
notion image
notion image
app.get('/search', (req, res) => {
  const { q } = req.query;
  if (!q) {
    res.send('NOTHING FOUND IF NOTHING SEARCHED!');
  }
  res.send(`<h1>Search results for: ${q}</h1>`);
});
쿼리스트링 없는 경우 처리

336. Nodemon을 사용한 자동 재시작

코드를 변경할 때마다 재시작하지 않으려면?

nodemon

  • nodemon을 실행하면 노드 대신 파일의 변경 사항을 체크하고, 변화를 감지할 때마다 서버를 재시작한다.
npm i -g nodemon
nodemon -v
notion image
  • 파일을 변경하고 저장할 때마다 nodemon이 이를 감지하고 재시작한다.
 

337. 섹션 주제

  • Express 앱은 매우 기본적이며, HTML이나 텍스트의 문자열에 대해 응답
  • 완전한 응답 페이지나 완전한 웹사이트가 아님
  • 응답할 수 있는 수준 높이기 ⇒ 템플레이팅
  • EJS (Embedded JavaScript) templates 알아보기
    • 템플릿 구축을 위해 JavaScript 코드, Excel JavaScript 구문을 HTML 내부에 포함할 수 있는 것
 

338. 템플레이팅이란?

  • 정적인 HTML 코드를 쓰는 대신 정보와 로직을 넣어서 루프로 여러 번에 걸쳐 템플릿을 반복하는 것을 말한다.
  • 최종 목표는 특정 로직과 HTML 응답 생성을 결합하는 것
  • Nunjucks
  • Pug
  • Handlebars
  • 변수 추가 후 HTML에 평가될 이중 중괄호 {{ }} 사용. 조건문 및 루프도 사용 가능
 

339. EJS용 Express 구성하기

  • 새 Express 앱 만들기 npm init -y npm i express
  • Express에 우리가 사용하고자 하는 템플레이팅 엔진을 알리기
    • npm i ejs 설치
    • app.set(’view engine’, ‘ejs’); view 엔진을 ejs로 설정하면 ejs 파일을 찾는다.
    • mkdir views touch views/home.ejs
  • app.get이나 라우트 안에 문자열 대신 파일, 템플릿 보내기
    • res.render 메서드
      • const express = require('express');
        const app = express();
        
        app.set('view engine', 'ejs');
        
        app.get('/', (req, res) => {
          res.render('home')
        });
        
        app.listen(3000, () => {
          console.log('LISTENING ON PORT 3000');
        });
 

340. 뷰 디렉토리 설정하기

  • 현재 views의 디렉토리 확인하기 process.cwd()
  • index.js 뒤에 /views가 붙게 경로 설정하기
    • const path = require('path'); path 모듈 불러오기
      • path 모듈은 파일과 디렉토리 경로에 관한 메서드를 제공
    • path의 join 메서드로 전체 디렉토리에 /views 경로 붙이기
      • const express = require('express');
        const app = express();
        const path = require('path');
        
        app.set('view engine', 'ejs');
        app.set('views', path.join(__dirname, '/views'))
        
        app.get('/', (req, res) => {
          // res.send('HI');
          res.render('home')
        });
        
        app.listen(3000, () => {
          console.log('LISTENING ON PORT 3000');
        });
        nodemon을 실행시킨 위치가 아니라 index.js가 있는 디렉토리를 사용하게 하는 과정
 

341. EJS 보간 구문

https://ejs.co/
  • EJS language support 익스텐션 설치
  • <%= %> 구문 - 평가된 값을 템플릿으로 출력해 준다.
    • 간단한 연산, 메서드 넣어보기
    • <%= 4 + 5 + 1 %> <%= 'hello world'.toUpperCase() %>
  • 보통은 데이터베이스나 템플릿에 제공될 다른 곳의 데이터를 사용할 때 쓰인다.
 

342. 템플릿에 데이터 전달하기

<body>
    <h1>Your random number is:
        <%= Math.floor(Math.random() * 10) + 1 %>
    </h1>
</body>
  • 난수를 넘겨줘서 렌더링 하는 방법
app.get('/rand', (req, res) => {
  const num = Math.floor(Math.random() * 10) + 1;
  res.render('random', { rand: num });
});
index.js
<h1>Your random number is: <%= rand %></h1>
random.ejs
  • 라우트에서 res.render 의 두 번째 인수를 통해 템플릿으로 데이터를 넘기면, 템플릿에서는 해당 이름의 변수에 접근할 수 있다.
 

343. 서브레딧 템플릿 데모

  • res.params 활용
app.get('/r/:subreddit', (req, res) => {
  const { subreddit } = req.params;
  res.render('subreddit', { subreddit });
});
index.js
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= subreddit %></title>
</head>

<body>
    <h1>Browsing The <%= subreddit %> Subreddit</h1>
</body>
</html>
subreddit.ejs
notion image
 
 
 

 
 
본 스터디는 Udemy의 <【한글자막】 The Web Developer 부트캠프 2022> 강의를 활용해 진행됐습니다. 강의에 대한 자세한 정보는 아래에서 확인하실 수 있습니다.
 
 
프밍 스터디는 Udemy Korea와 함께 합니다.
 
 

 
원하는 스터디가 없다면? 다른 스터디 개설 신청하기
누군가 아직 원하는 스터디를 개설하지 않았나요? 여러분이 직접 개설 신청 해 주세요!
이 포스트는
"웹 개발 실전 프로젝트" 스터디의 진행 결과입니다
진행중인 스터디
웹 개발 실전 프로젝트
JavaScript를 이용한 DOM 조작으로 프론트엔드 분야를 배우고, Node JS와 Express, DB, HTTP, REST API 등 백앤드 지식을 학습할수 있습니다. 이론만 공부하는 스터디가 아니라 13개 이상의 프로젝트로 이루어진 실습 위주의 커리큘럼으로 개인 프로젝트 결과물을 가져갈 수 있다는 메리트가 있습니다.
조재홍 멘토
이로운 개발자가 되고 싶은 조재홍입니다.