Firebase는 모바일 및 웹 애플리케이션 개발 플랫폼으로, 분석, 데이터베이스, 메시징, 오류 보고 등의 많은 기능을 제공한다.

이중에서 웹 개발과 관련된 기능을 쉽게 익힐 수 있도록

데이터 입출력(CRUD)을 중심으로 간단한 게시판 예제를 작성하는 방법을 정리하고,

이 웹 애플리케이션을 무료로 호스팅(hosting)하는 방법을 다음과 같이 단계별로 정리하였다.


1. 시작하기

2. Realtime Database 기반 게시판

3. Cloud Firestore (beta) 기반 게시판

4. 로그인 (authentication) 기능 추가

5. 무료 호스팅(Firebase Hosting)으로 배포 I, II


이 예제는 Node.js와 Express를 기반으로 작성하였고,

이전에 정리한 Node.js 게시판(Express + MariaDB / MySQL) 예제를 사용하였다.

데이터베이스를 MariaDB / MySQL 대신에 Firebase에서 제공하는 Realtime Database / Cloud Firestore 로 수정한 예제이다.

소스는 GitHub에서 받을 수 있다.


먼저 console 창에서 다음과 같이 입력하여 사용할 프로젝트를 간단하게 생성한다.

프로젝트 폴더 명(firebaseExample)은 수정해도 된다.

주의: Node.js, express, express-generator가 설치되어 있어야 한다.

> express --session --view=ejs --css css firebaseExample

> cd firebaseExample

> npm install

콘솔창에서 [npm start]를 입력하여 웹 서버를 실행한다.

웹 브라우저에서 [http://localhost:3000/]를 입력하여 다음 그림과 같이 실행되면,

제대로 Node.js용 웹 프로젝트가 생성된 것이다.


웹브라우저로 Firebase 콘솔(https://console.firebase.google.com/)에 접속한다.

주의: Firebase 콘솔을 사용하기 위해서는 gmail 계정이 있어야 한다.


[프로젝트 추가]를 선택해서,

프로젝트 이름(firebaseExample)과 지역(대한민국)을 입력하고 [프로젝트 만들기]를 실행한다.



생성과 동시에 그림과 같이 생성된 프로젝트가 선택되어, 해당 프로젝트에서 사용할 수 있는 기능(왼쪽 메뉴)들이 나열된다.


화면 중앙에 있는 것은 Firebase에서 지원하는 언어로, Node.js를 사용할 것이기 때문에 [웹 앱에 Firebase 추가]를 선택한다.


원격에서 Firebase에 접속하기 위해 필요한 정보가 자바 스크립트 코드로 제공된다.

이것을 복사한다.


편집기를 실행해서,

firebaseExample\routes 폴더에 있는 index.js 파일을 다음과 같이 추가한다.

test라는 URL을 추가하였다 [라인 9~11].

test는 test라는 HTML(ejs) 파일을 호출하기[라인 10] 때문에 views 폴더에 test.ejs를 다음과 같이 작성한다.

4 라인은 Firebase에서 복사한 코드 중 Firebase을 웹(HTML)에서 사용하기 위해 추가한 Firebase용 자바 스크립트 라이브러리이다.

복사한 코드 중 config 변수의 내용을 위 코드와 같이 그대로 붙여넣기 해서 사용하면 된다 [라인 10~18].


Firebase를 초기화(접속)하고 나면 [라인 18],

Firebase에서 제공하는 Realtime Database에 데이터를 저장한다.

데이터를 저장하기 위해 JSon 형대로 데이터를 구성한다 [라인 22~28].

하나의 JSon이 하나의 행이 되고,

각각의 키(brdno, brdwriter, brdtitle, brdmemo, brddate)가 필드가 된다.


게시판에는 여러 개의 게시물이 저장 될 수 있기 때문에

각각의 데이터(행)을 구분하기 위해 Firebase에서 제공하는 고유값을 받아서 [라인 20]

Firebase 데이터베이스(Realtime Database)에 저장한다 [라인 33].

자세한 설명은 Firebase 문서에 잘 정리되어 있다.


웹브라우저에서 Firebase 콘솔(https://console.firebase.google.com/)에 접속한다.

왼쪽 메뉴에서 Database를 선택한다.

Cloud Firestore (beta) 와 Realtime Database 중에서 우측에 있는 Realtime Database의 [시작하기]를 선택한다.


운영일때는 [잠금 모드로 시작]을 사용하지만, 편의를 위해 [테스트 모드로 시작]을 선택하여 생성한다.


다음 그림과 같이 빈(null) 데이터 저장소를 볼 수 있다.


다른 웹브라우저(새탭)에서 앞서 작성한 URL(test)를 다음 그림과 같이 실행한다.

http://localhost:3000/test

test는 Firebase에 하나의 데이터(행)을 추가하도록 작성한 컨트롤(url)이므로,

Firebase 콘솔에서 데이터가 추가되었는지 확인한다.


board라는 노드 밑에 고유값(newPostKey)으로 생성된 노드가 있고, 그 하위에 앞서 작성한 데이터가 저장되어 있다.

test.ejs에서 작성한 다음 코드 구조데로 저장된 것이다 [라인 31].

updates['/board/' + newPostKey] = postData;

Json 구조라 무한데로 계층화 시켜서 저장할 수 있는데

board를 테이블(table), 고유값(Key)을 행으로 의미를 부여해서 저장하였다.

이 구조로 게시판 예제를 작성할 것이다.


본격적인 예제를 작성하기 위한 준비로 Node.js(express)를 설치하고,

HTML(ejs) 파일에서 Realtime Database에 접속하여 데이터를 추가하는 것까지 정리하였다.

Firebase 문서의 이름으로 정리하면 웹에서 Firebase를 사용한 예제를 작성한 것이다.

다음으로 Realtime Database을 이용하여 간단한 구조의 게시판 예제를 작성해 본다.


정보: HTML 파일이기 때문에 Node.js가 없어도 단복으로 실행하거나 JSP, ASP, PHP등에서 실행할 수 있다.



앞서 생성한 firebaseExample 프로젝트에 간단한 게시판 기능을 구현하며 기초적인 Firebase(Realtime Database) 사용법을 정리한다.

Node.js를 이용하여 Firebase의 Realtime Database에 데이터를 저장하고 조회하는 CRUD 게시판을 제작한다.

관련된 상세한 설명은 Firebase에 정리되어 있고, 여기서는 이용하는 코드만 정리한다.

관련 소스는 GitHub에서 받을 수 있다.


1. 시작하기

2. Realtime Database 기반 게시판

3. Cloud Firestore (beta) 기반 게시판

4. 로그인 (authentication) 기능 추가

     5. 무료 호스팅(Firebase Hosting)으로 배포 I, II


앞서의 간단한 예제에서는 Firebase 라이브러리를 자바스크립트로 가져와서 사용한 예제로 별도의 설치가 없었지만,

Node.js에서 사용하기 위해서는 Firebase를 설치해야 한다.

firebaseExample 폴더에서 다음 명령어를 실행하여 Firebase를 설치한다.

npm install firebase --save


다음으로 app.js 파일을 열어서 다음 문장을 추가한다.

app.use('/board1', require('./routes/board1'));

routes 폴더에 있는 board1.js 파일을 board1 이라는 가상 폴더(/)로 사용하겠다는 것이다.

웹에서 board1/list, board1/form 과 같이 입력해서 사용한다.


board1.js의 내용은 다음과 같다.

board1.js

먼저, 콘솔에서 다음 명령어로 dateformat 라이브러리를 설치한다.

dateformat은 위 코드에서 저장된 날짜를 형식에 맞추어 출력하기 위해 사용되었다 [라인 4, 27, 39].

    npm install dateformat --save

저장 할때는 자바스크립트의 now()함수로 년월일시분초(ms)까지 저장하고,

웹 페이지에 출력할때에는 dateformat을 이용하여 년월일만 출력한다 [라인 27, 39].


Firebase의 Realtime Database를 이용하여 게시판을 만들기 위해,

앞서 테스트로 입력한 것처럼 Firebase에서 복사해온 config  값들을 붙여넣기한 후 초기화 한다 [라인 10~18].


본격적인 게시판과 관련된 코드로

데이터 CRUD(Create, Read, Update, Delete)를 구현하기 위해

boardList [라인 20], boardRead [라인 33], boardForm[라인 43], boardSave[라인 57], boardDelete[라인 73]의 5 개의 컨트롤(URL)을 정의하였다.

게시판 구성에 대한 것은 이전에 정리하여 여기서 따로 정리하지 않는다.


boardList는 글 리스트를 출력하는 컨트롤로 Realtime Database에 저장된 데이터를 가져와서 화면에 출력한다.

Realtime Database에 접근하기 위해 firebase.database()를 사용하고 [라인 21],

Realtime Database에서 board의 데이터를 모두 가져오도록(once) 했다[라인 21].

가지고 온 데이터는 키(key)와 데이터로 구성된 변수 snapshot으로 반환되는 데,

snapshot을 그대로 HTML (ejs)로 보낼 수 없어 배열 rows로 변환해서 사용한다.


별도의 변환 함수가 있는 것이 아니고,

snapshot의 개수 만큼 반복해서(forEach) [라인 23]

키는 제외하고 데이터만 추출해서(val()) [라인 24], rows에 추가한다 [라인 27].

변환이 완료되면, views의 board1폴더에 있는 boardList.ejs에 rows를 파라미터로 지정해서 호출한다 [라인 29].


게시판 리스트 구현에서 Realtime Database의 단점을 확인 할 수 있다.

orderByKey는 정렬을 의미하는데 [라인 21] 내림차순(DESC)이 제공되지 않는다.

즉, 최신글이 가장 앞에 나오는 기능을 구현할 수 없다.

검색 기능도 제한적이라 페이징 등의 처리를 구현하기 어려운 문제가 있다.

리스트와 관련된 상세한 내용은 Firebase 문서를 참고 하면 된다.


호출된 boardList.ejs [라인 29] 에서는 다음과 같이 작성되어 있다.

boardList.ejs

대부분 화면 출력을 위한 HTML 코드이고

라인 27 부터 36 이 컨트롤에서 넘겨진 데이터(rows)의 값을 적절하게 디자인해서 출력하는 코드이다.

rows 가 배열이니 배열의 개수 만큼 반복해서(for) [라인 27],

각각의 필드(brdno, brdtitle, brdwriter, brddate) 값을 화면에 출력(=)한다 [라인 30~33].


Node.js를 재가동하고(npm start), 웹 브라우저에서 http://localhost:3000/board1/boardList를 입력하여

앞서 테스트로 입력한 데이터가 그림과 같이 출력되는지 확인한다.


다음으로 글쓰기 기능을 구현한다.

글쓰기는 사용자가 입력하는 화면(boardForm)과

사용자가 저장 버튼을 눌렀을 때, 입력한 값을 받아서 저장(boardSave)하는 부분으로 구성된다.


board1.js의 코드를 보면 사용자가 입력하는 화면에서는 두 가지 기능을 처리한다.

글번호(brdno) 값이 없으면 신규(new) 글쓰기로 별 다른 처리 없이 진행하고 [라인 44~47],

값이 있으면 글 수정을 위해 글번호에 해당하는 데이터를 가지고 와서 화면에 출력한다 [라인 49~54].

신규일 경우에는[라인 44] boardForm.ejs 파일을 호출하고 끝나지만 [라인 45].

수정일 경우에는 firebase.database()에서 파라미터로 지정된 글번호(req.query.brdno)를 지정해서 하나의 값만 가지고 온다 [라인 49].

데이터를 가지고 오는 것이 글 리스트의 사용법과 동일하지만 [라인 21],

Board 뒤에 키 값을 넣어서 하나의 데이터만 가지고 오도록 하는 차이가 있다.


boardForm.ejs는 컨트롤에서 넘겨준 값(row)를 화면에 출력하는 기능을 한다.

boardForm.ejs

사용자가 새 글을 쓰는 경우에는 단순하게 HTML을 출력하고,

수정일 경우에는 넘겨 받은 값(row)를 출력하기 위해 각각의 필드 값을 지정한다 [라인 17, 21, 25].

신규인지 수정인지를 판단하기 위해 글 번호(고유값, PK) 값을 보관하는데 [라인 30],

작성일자(brddate)도 hidden 필드로 값을 보관해야 한다 [라인 31].

이것도 Realtime Database의 단점으로,

Realtime Database는 수정하는 필드 값만 지정해서 사용할 수 없다.

저장시 지정된 필드(Json 키)만 저장하기 때문에 수정시 JSon 구성에 필드 지정이 안되어 있으면 저장되지 않는다.

글 수정시 작성자, 글제목, 글내용만 수정했다고 이것만 저장하면 작성일자는 삭제된다는 의미이다.

이 예제는 필드가 몇개 없어서 hidden으로 추가하면 되지만, 실제 사용중에는 몇 십개의 필드를 사용하는 경우가 많다.

이 경우 일반적인 RDBMS처럼 구현할 수 없고, Realtime Databas는 다른 방법을 사용해야 한다.


글을 작성하고 저장 버튼을 누르면 board1.js의 boardSave 컨트롤이 호출된다 [라인 57~71].

boardSave에서는 사용자가 작성한 값을 저장(set)한다 [라인 65].

저장하기 전에, 글번호(brdno) 값이 없으면 새 글 작성으로 보고 키 값 [라인 60]과 작성일자 [라인 61]의 값을 생성한다.

글 수정일 때는 입력화면에서 받은 값을 그대로 저장하면 되는데,

HTML에서 작성일자 값이 문자로 넘어오기 때문에 다시 숫자로 변환하는 처리를 해준다 [라인 63].

Realtime Database에 저장하기 위해서는 데이터가 Json 형태로 저장되어야 하는데,

Node.js(express)에서 사용자가 입력한 값을 Json으로 넘겨 주기 때문에, 그대로(postData = req.body) 사용한다 [라인 58].


앞서의 테스트에서는 update를 사용했지만 [라인 66~68], 글 저장에서는 set을 사용하였다 [라인 65].

Update는 여러 개의 Json 데이터를 한번에 저장할 때 사용하고,

Set은 한번에 하나의 데이터를 저장할 때 사용한다.

자세한 사용법은 Firebase 문서에 정리되어 있다.


저장된 데이터는 글 리스트와 Firebase 콘솔에서 확인 할 수 있다.



글 읽기(boardRead)는 글 리스트에서 하나의 행(글번호)을 선택하면 상세한 내용을 출력하는 페이지이다 [라인 33~41].

지정된 글번호(brdno)을 가지고 오는 것은 글 수정과 동일하고 [라인 49~54], 화면에 출력하는 방법은 다음과 같다.

boardRead.ejs

boardRead.ejs의 내용은 글 수정과 비슷하게 작성하는데

글 수정은 사용자가 수정할 수 있도록 입력상자(text)를 사용하고, 글 읽기는 단순하게 지정된 값을 화면에 출력한다 [라인 16, 20, 24].


마지막으로 글삭제(boardDelete)는 주어진 글번호(brdno) 값을 remove()함수로 삭제한다 [라인 74].

글 삭제는 별도의 화면이 없고, 삭제 후 글 리스트로 이동한다 [라인 75].


이상으로 Realtime Database을 이용하여 Node.js(Express)에서 간단한 게시판 기능을 구현하였다.

제법 그럴 듯 해 보이지만, 단순한 기능 구현에도 두 가지 문제가 나타났다.

1. 내림차순 정렬 안됨

2. 데이터 수정시 모든 필드를 나열해야 함

정리하지 않았지만 이외에도 많은 단점이 보였다.


개인적으로는 Realtime Database는 단순한 정보를 저장하는 기능에 적합한 것 같고, 복잡한 데이터 처리에는 맞지 않는 것 같다.

다만, 이러한 문제들을 해결하기 위해 Cloud Firestore(베타)가 새로 나온 것 같다.






Firebase는 Realtime Database외에 Cloud Firestore (beta)라는 클라우드 기반 데이터베이스 솔루션을 제공한다.

구글에 따르면 Cloud Firestore는 유연하고 확장 가능한 NoSQL 클라우드 데이터베이스라고 하고

두 데이터 베이스에 대한 차이는 Firebase 문서에 잘 정리되어 있다.


이번에는 앞서 생성한 예제(firebaseExample) 프로젝트의 Realtime Database를 Cloud Firestore로 변환하여 간단한 게시판 기능을 구현한다.

앞서 작성한 Realtime Database 예제에서 Realtime Database에 접속하여 사용하는 코드만

Cloud Firestore 용 코드로 수정하는 방식으로 정리하기 때문에 이전 내용을 먼저 숙지 해야 한다.

관련된 상세한 설명은 Firebase 문서를 참고하면 되고, 관련 소스는 GitHub에서 받을 수 있다.


1. 시작하기

2. Realtime Database 기반 게시판

3. Cloud Firestore (beta) 기반 게시판

4. 로그인 (authentication) 기능 추가

5. 무료 호스팅(Firebase Hosting)으로 배포 I, II


Cloud Firestore (beta)를 사용하기 위해 Firebase 콘솔의 Database 관리화면에서 상단에 있는 [Realtime Database]를 선택한다.

그림과 같이 Realtime Databas와 Cloud Firestore 선택 화면이 나타난다.

(왼쪽에 있는 [Database] 메뉴를 선택해도 된다.)


Cloud Firestore를 선택하면 다음과 같이 화면이 출력된다.

실제로는 [잠금모드로 시작]을 선택해야 하지만, 편의상 [테스트 모드로 시작]을 선택한다.


Realtime Databas가 Cloud Firestore로 변경되어 출력되면 사용할 준비가 된 것이다.


하나의 앱(웹)에 하나의 Firebase의 프로젝트가 사용될 수 있기 때문에,

다음과 같이 app.js에서 board1 (Realtime Databas예제)을 주석처리하고 board2 (Cloud Firestore 예제)를 추가해 준다.

전체 코드를 보기 위해 각각의 js 파일에서 Firebase에 접속하도록 작성했기 때문에 모든 예제를 동시에 실행할 수 없다.

실제 사용할때는 하나의 데이터 베이스만 선언해서 사용하도록 구현해야 한다.


board1과 board2는 대부분 동일한 코드로 구성되었고, 데이터 베이스 관련 부분만 차이가 있다.

따라서 board1.js와 board2.js의 차이나는 부분만 정리한다.

대부분의 HTML(ejs)는 동일하기 때문에 board1 폴더를 그대로 복사해서 사용하면 된다.


먼저, board2.js의 코드는 다음과 같다.

board2.js

Realtime Databas와 Cloud Firestore의 차이는 Firebase 문서에 잘 정리되어 있다.

사용한 코드 상의 차이를 몇 개만 정리하면,

Realtime Databas는 firebase.database()로 데이터를 조작하고

Cloud Firestore는 firebase.firestore()로 조작한다 [라인 19].

board1에서는 firebase.database()을 그대로 사용했고

board2에서는 firebase.firestore()를 db 변수로 선언해서 사용한다 [라인 19].


주의: Firebase 서버에 접속하는 방법에도 차이가 있지만 여기서 정리하지 않고 Hosting에서 다시 정리한다.

Firebase에서는 Firestore 사용시 서비스 계정을 이용을 추천하는데, 시작하는 수준에서 너무 많은 내용을 다루면 복잡할 것 같아서 정리하지 않는다.


데이터를 가지고 오는 코드에도 차이가 있다.

Realtime Databas는 Json 노드 방식으로 저장을 하고,

Cloud Firestore는 컬렉션(collection)이 있고, 그 하위에 문서(Document), 그리고 Json 데이터가 필드로 저장된다.

다음 그림과 같이 board 컬렉션에 고유값으로 생성된 문서(gprx1Fq0apDB6tQFv7rb), 그리고 게시판  데이터(Json)가 저장된다.

즉 일반적인 관계형 데이터 베이스로 이야기 하면, board 테이블에 하나의 행(문서)이 Json구조로 저장되는 것이다.

데이터 행 구분을 Realtime Database와 동일하게 고유값으로 구별하여 작성하였다.


실제 구현된 코드를 보면 다음 코드와 같이 FireStore에서 조회된 데이터 처리는 동일하다.

데이터를 조회하는 함수 이름에 조금의 차이가 있다.

router.get('/boardList', function(req, res, next) {
    db.collection('board').orderBy("brddate", "desc").get()
        .then((snapshot) => {
            var rows = [];
            snapshot.forEach((doc) => {
                var childData = doc.data();
                childData.brddate = dateFormat(childData.brddate, "yyyy-mm-dd");
                rows.push(childData);
            });
            res.render('board2/boardList', {rows: rows});
        })
        .catch((err) => {
            console.log('Error getting documents', err);
        });
});

board로 콜렉션(collection)을 지정하고, once가 아닌 get을 사용한다.

가장 큰 차이는 올림차순(desc) 정렬이 가능하다는 것이다.

이외에도 다양한 조건에 의한 검색이 가능하고, 상세한 설명은 Firebase 문서에 정리되어 있다.


글 읽기(baordRead)와 글 수정(baordForm)의 데이터 조회도 리스트 코드와 동일해서 따로 정리하지 않는다.


Realtime Databas와 Cloud Firestore의 차이 중 하나는 저장 방식에 있다.

Cloud Firestore에서는 새글을 작성할때,

컬렉션을 지정하고(db.collection("board")) 문서 함수를 (db.collection("board").doc())를 호출하면,

새로운 문서가 생성되고 고유 번호(doc.id)가 자동으로 부여된다.

router.post('/boardSave', function(req,res,next){
    var postData = req.body;
    if (!postData.brdno) {  // new
        postData.brddate = Date.now();
        var doc = db.collection("board").doc();
        postData.brdno = doc.id;
        doc.set(postData);
    } else {                // update
        var doc = db.collection("board").doc(postData.brdno);
        doc.update(postData);
    }
   
    res.redirect('boardList');
});

글을 저장할때 사용하는 set과 update의 의미가 Realtime Databas와 차이가 있다.

set은 기존에 데이터가 있던 지, 없던지 새로 작성하고(있으면 삭제),

update은 기존 데이터를 수정하여 저장한다.

특히, update는 모든 필드가 지정되지 않아도 지정된 필드만 수정한다.

앞서 Realtime Databas의 글 저장 예제에서 처럼 brddate을 HTML(boardForm.ejs)에서 hidden 필드로 가지고 있을 필요가 없다.


이상으로 Cloud Firestore를 이용하여, Realtime Database와 유사하게 게시판 기능을 구현하였다.

Cloud Firestore는 검색 엔진인 루씬(Lucene)의 향기를 풍기며, 제법 강력한 데이터베이스 기능을 제공하고 있는 것 같다.

보다 상세한 기능은 문서를 읽어보길 바라고,

특히 데이터 쿼리 문서를 읽어 보면 제법 그럴듯한 게시판을 구현 할 수 있을 것 같다. (이건 각자 ~~)


주의: Node.js 6 이하의 버전에서는 다음과 같은 오류가 발생한다.

Function DocumentReference.update() called with invalid data. Data must be an object, but it was: a custom object
FirebaseError: Function DocumentReference.update() called with invalid data. Data must be an object, but it was: a custom object
    at FirestoreError.Error (native)


json 데이터 형식이 맞지 않아서 생기는 오류로 다음과 같은 변환 처리를 해줘야 한다.

router.post('/boardSave', function(req,res,next){
    var postData = JSON.parse( JSON.stringify(req.body));

    생략 ~~


콘솔창에 다음과 같은 경고 메시지도 출력된다.

Firebase 초기화후에 다음과 같이 설정하면 위 경고 메시지를 제거할 수 있다.

생략 ~~

firebase.initializeApp(config);
var db = firebase.firestore();
db.settings({timestampsInSnapshots: true})

생략 ~~

최신 버전의 Node.js를 사용하면 위와 같은 문제가 생기지 않지만,

마지막에 다룰 무료 호스팅(Firebase Hosting)Node.js 6을 지원한다. 최신 버전을 지원하지 않는다.






이번에는 Firebase 인증(Authentication) 기능을 이용하여, 실제 게시판처럼 로그인한 사용자만 사용하도록 작성한다.


1. 시작하기

2. Realtime Database 기반 게시판

3. Cloud Firestore (beta) 기반 게시판

4. 로그인 (authentication) 기능 추가

5. 무료 호스팅(Firebase Hosting)으로 배포 I, II


Firebase에서는 인증과 관련된 아주 많은 기능을 제공하고 있다.

회원 가입, 회원 리스트 등의 기능뿐만 아니라 페이스북, Gmail등의 로그인 기능 등을 제공하고 있다.

너무 많은 기능이 있고, 이것을 문서로 제공해서 오히려 익히기 어렵게 느껴지기 때문에 여기에서는 로그인 기능만을 정리한다.

따라서, 앞서 작성한 Cloud Firestore 기반 게시판에 로그인 기능과 로그인한 사용자만 사용하도록 구현한다.

관련된 상세한 설명은 Firebase 인증 문서를 참고하면 되고, 관련 소스는 GitHub에서 받을 수 있다.


먼저, Firebase 콘솔에서 Authentication(인증)을 선택하고, 화면 하단에 있는 [로그인 방법 설정]을 선택한다.


로그인 제공업체 리스트에서 [이메일/비밀번호]를 선택한다.


첫 번째로 보이는 [사용 설정]을 활성화 시키고 저장한다.


[사용자] 탭을 클릭해서 [사용자 추가] 버튼을 클릭하고,

실행된 팝업창에서 로그인 ID (이메일)와 비번번호를 입력한다.


다시 화면 중앙에 있는 [사용자 추가] 버튼을 클릭해서 사용자를 생성한다.


간단하게 사용자(test@test.com)를 생성하였다.

프로그램으로 생성하고 관리하는 방법은 Firebase 문서를 참고하면 된다.

아쉬운 점은 사용자 이름(별명-displayName)을 입력하는 방법은 프로그램으로 처리해야 한다는 것이다.


board2.js와 board2 폴더를 복사해서 board3.js와 board3 폴더를 생성한다.

app.js에서 다음과 같이 기존 컨트롤들은 주석 처리하고, board3을 활성화 한다.

app.use('/', indexRouter);
app.use('/users', usersRouter);
//app.use('/board1', require('./routes/board1'));
//app.use('/board2', require('./routes/board2'));
app.use('/board3', require('./routes/board3'));

app.js


로그인 기능을 구현하기 위해 다음 코드를 board3.js에 추가한다.

사용자의 로그인 정보를 입력받을 화면(loginForm)와 로그인 처리를 수행할(loginChk) 컨트롤을 구현한 것이다.

router.get('/loginForm', function(req, res, next) {
    res.render('board3/loginForm');
});

router.post('/loginChk', function(req, res, next) {
    firebase.auth().signInWithEmailAndPassword(req.body.id, req.body.passwd)
       .then(function(firebaseUser) {
           res.redirect('boardList');
       })
      .catch(function(error) {
          res.redirect('loginForm');
      });   
});

용자의 로그인 정보를 입력받을 화면(loginForm)은 별다른 처리 없이 HTML (loginForm.ejs) 페이지를 호출한다.


loginForm.ejs

사용자의 아이디(id)와 비밀번호(passwd)을 입력받도록 구성하면 된다 [라인 17, 21].


loginChk에서는 signInWithEmailAndPassword 함수로 사용자가 입력한 아이디와 비밀번호를 Firebase에 전송해서

맞으면 (then) 게시판 리스트로 가고, 틀리면(catch) 다시 입력하도록 구현했다.


http://localhost:3000/board3/loginForm로 접속해서 로그인이 잘되는지 확인해 본다.

앞서 Firebase 콘솔에서 입력한 아이디(test@test.com)와 비밀번호(test1234)을 이용해서 확인하면 된다.


board3.js 파일에 다음과 같이 로그인되지 않았으면 (! firebase.auth().currentUser),

로그인 창으로 이동하는(res.redirect('loginForm')) 코드를 추가한다.

~~ 생략 ~~

router.get('/boardList', function(req, res, next) {
    if (!firebase.auth().currentUser) {
        res.redirect('loginForm');
        return;
    }
    db.collection('board').orderBy("brddate", "desc").get()
          .then((snapshot) => {
    ~~ 생략 ~~   
});


router.get('/boardRead', function(req, res, next) {
    if (!firebase.auth().currentUser) {
        res.redirect('loginForm');
        return;
    }
   
    db.collection('board').doc(req.query.brdno).get()
    ~~ 생략 ~~   
});

router.get('/boardForm', function(req,res,next){
    if (!firebase.auth().currentUser) {
        res.redirect('loginForm');
        return;
    }
   
    ~~ 생략 ~~   
});

router.post('/boardSave', function(req,res,next){
    var user = firebase.auth().currentUser;
    if (!user) {
        res.redirect('loginForm');
        return;
    }
   
    var postData = req.body;
    if (!postData.brdno) {  // new
        postData.brddate = Date.now();
        var doc = db.collection("board").doc();
        postData.brdno = doc.id;
        postData.brdwriter = user.email;
        doc.set(postData);
    ~~ 생략 ~~   
});

router.get('/boardDelete', function(req,res,next){
    if (!firebase.auth().currentUser) {
        res.redirect('loginForm');
        return;
    }
   
    ~~ 생략 ~~   
});

board3.js

로그인 여부를 확인하는 코드는 모두 동일하다.

현재 로그인한 사용자(currentUser)가 있으면 로그인한 상태로 판단한다.

다만, boardSave에서는 조금 차이가 있는데,

현재 로그인한 사용자(currentUser)를 user 변수로 받아서 사용한다.

user 변수로 사용하는 이유는 작성자(brdwriter) 필드에 아이디(email)을 넣어주기 위해서 이다.

이전에는 사용자를 확인할 수 없어서 글 작성시 사용자가 입력했지만,

로그인 기능을 구현하였기 때문에, 위 그림과 같이 별도로 입력받지 않고 로그인한 정보를 이용한다.


새로운 글을 추가하면 다음과 같이 작성자(writer)에 현재 로그인한 사용자의 아이디가 나타난다.

이상으로 간단하게 로그인 기능과 로그인한 사용자만 글을 읽고 쓰도록 구현했다.

자신의 글만 수정하고 삭제 하는 등의 기능도 구현해 보면 실력향상에 도움이 될 것이다.

회원 관리 기능들도 구현해서 사용자의 이름(displayName)을 사용할 수 있도록 하면 제법 그럴 듯 하게 보일 것이다.


마지막으로 Firebase에서 제공하는 무료 호스팅(Hosting)을 정리한다.

Firebase 호스팅은 Node.js 기반으로 제공되며, 자세한 사항은 Firebase 호스팅 관련 문서를 참고하면 된다.


1. 시작하기

2. Realtime Database 기반 게시판

3. Cloud Firestore (beta) 기반 게시판

4. 로그인 (authentication) 기능 추가

5. 무료 호스팅(Firebase Hosting)으로 배포 I, II


Firebase 호스팅은 무료이고 간편하게 사용할 수 있는 장점이 있지만,

Node.js 6 (최신 버전이 8)을 지원하는 단점과 Express 사용이 조금 불편하고 제약이 있는 등의 단점도 있다.


먼저, Firebase 콘솔의 Hosting 메뉴에서 [시작하기] 버튼을 선택한다.


firebase-tools을 설치하라는 메시지가 출력된다.

설치 문장을 복사해서 메모장 등에 보관하고 [계속] 버튼을 클릭한다.


콘솔에서 firebase 호스팅을 제어하는 명령어가 제공된다.

3 개의 명령어를 복사해서 보관하고, [계속] 버튼을 클릭한다.


마지막으로 호스팅 대시보드가 출력된다.

처음의 도메인 부분에서 Firebase 호스팅에서 제공하는 도메인 외에 다른 도메인을 사용하도록 설정할 수 있다.

다음의 배포 기록은 클라이언트(개발자 PC등)에서 배포(deploy)한 기록이다.


PC의 콘솔에서 앞서 복사해 두었던 문장을 이용하여 Firebase 도구를 설치한다.

> npm install -g firebase-tools


Firebase 도구(CLI)를 설치하면, 콘솔에서 Firebase 명령어들을 실행할 수 있다.

Firebase 도구(CLI)는 Firebase 프로젝트 관리, 조회, 배포를 위한 여러 가지 도구를 제공하므로 보다 상세한 설명은 관련 문서를 참고하면 된다.

> firebase login

> firebase init

> firebase deploy


login은 PC에서 Firebase 웹 콘솔을 자유롭게 이용하기 위해 Firebase에 접속하는 것으로 gmail 계정등을 입력한다.

콘솔에서 firebase login를 실행하면 웹 브라우저가 실행되어, 다음 그림과 같이 로그인 화면이 실행된다.

처음 한번만 로그인하면 자유롭게 사용할 수 있다.

init는 호스팅 하기 위한 프로젝트를 생성(초기화)하기 위해 사용하고

deploy는 작성한 프로젝트를 서버에 배포할 때 사용한다.


firebase 호스팅을 사용하기 위해서 앞서 사용했던 firebaseExample 프로젝트(폴더) 대신에 firebaseHosting 폴더를 새로 생성한다.

그림과 같이 콘솔창에서 firebaseHosting로 이동(cd)한 후, firebase init를 입력하고 실행한다.

진행(Are you ready to proceed)를 묻는 질문에 Y를 입력하고 엔터키를 눌러 다음으로 넘어간다.


사용할 Firebase의 서비스를 선택하라는 메시지가 출력된다.

상하 화살표 키를 이용하여 이동하고, 스페이스 바를 이용하여 선택(*)과 해제( )를 할 수 있다.

Firestore, Function, Hosting을 선택하고 엔터키를 누른다.

Firestore는 게시판 예제 중 Firestore 기반 게시판 예제(board2)를 호스팅 할 것 이기 때문이고

Function은 Express 프레임워크를 사용하기 위해서 선택한다.


다음으로 해당 계정(gmail)이 사용할 수 있는 Firebase의 프로젝트 리스트가 출력되고 이중에서 호스팅으로 사용할 프로젝트를 선택하고 넘어간다.


기타 rules, index등은 엔터키를 쳐서 넘어가고 (기본값 사용), JavaScript와 Typescript 선택화면에서 JavaScript를 선택한다.


ESLint와 npm 종속성 설치에 Y를 선택한다.


public 폴더를 지정하고(엔터), 싱글페이지 설정은 하지 않고(No),

Firebase 초기화 설치를 종료한다.


탐색기에서 firebaseHosting 폴더를 열어보면, 그림과 같이 제법 많은 파일이 생성된 것을 볼 수 있다.

firebaseHosting폴더는 Node.js의 기본 폴더 구조와 비슷해 보이는데, node_modules폴더의 위치가 다르다.

firebaseExample에서는 firebaseExample 폴더 아래에 있지만,

firebaseHosting에서는 firebaseHosting폴더 하위의 functions 폴더하위에 있다.

이 차이로 인해 각종 라이브러리 설치시  (npm ~~)

firebaseExample에서는 firebaseExample 폴더에서 실행하고,

firebaseHosting에서는 firebaseHosting폴더 하위의 functions 폴더에서 실행해야 한다.

라이브러리가 설치되는 node_modules 폴더와 설치된 라이브러리 정보가 저장되는 package.json가 functions 폴더에 생성되어 있다.


firebase serve를 입력해서 웹 서버를 실행한다 (npm start가 아니다).


웹 브라우저에서 http://localhost:5000/를 입력해서 그림가 같이 출력되면 firebase 호스팅을 위한 Node.js 프로젝트가 제대로 설정된 것이다.


기본으로 작성된 index.html 파일 뿐이지만, 이것을 Firebase 호스팅 서버에 배포한다.

콘솔에서 firebase deploy를 입력해서 실행한다.


Firebase 콘솔에서 Hosting 메뉴을 실행해서 (firebaseExample 프로젝트), 하단의 추가된 배포 기록을 확인한다.


위 그림의 중앙에 있는 도메인 정보를 마우스로 클릭하나, 웹 브라우저에서 해당 도메인을 직접 입력한다.

다음 그림과 같이 앞서 로칼(PC)에서 실행한 것과 동일한 페이지가 실행되면 제대로 배포가 완료된 것이다.


이상으로 Firebse 호스팅의 기본적인 사항을 정리하였다.

이미지 양이 많아서 무엇인가 복잡해 보이지만, 조금 익숙해지면 사용법이 쉽고 간편하다는 것을 알 수 있다 (특히 배포).


앞서의 예제는 Express 프레임워크 기반으로 개발되어 Firebse 호스팅에서 그대로 사용할 수 없다.

Express 프레임워크을 사용하는 방법을 중심으로 보다 상세한 설치 방법은 다음 문서에서 정리한다.


다음은 Firebase 호스팅을 사용하기 위해 자주 사용하는 명령어이니 기억해두는 것이 좋다.

> firebase init

> firebase deploy

> firebase serve




지금까지는 Firebse 호스팅을 사용하는 기본적인 사용법을 정리하였다.

여기에서는 앞서 작성한 게시판 예제를 Firebse 호스팅에 적용하는 방법을 정리한다.

1. 시작하기

2. Realtime Database 기반 게시판

3. Cloud Firestore (beta) 기반 게시판

4. 로그인 (authentication) 기능 추가

5. 무료 호스팅(Firebase Hosting)으로 배포 I, II

앞서의 예제는 Express 프레임워크 기반으로 개발되어 Firebse 호스팅에서 그대로 사용할 수 없다.

Express 프레임워크을 사용하기 위해서는 몇 가지 설정을 수정해야 한다.


먼저, firebaseHosting 폴더에 있는 firebase.json 파일을 열어서 다음과 같이 수정해 준다.

{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint"
    ]
  },
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
       {
         "source": "/**",
         "function": "api1"
       }     
    ]
  }
}

firebase.json

새로 입력되는 URL들을 api1이라는 함수를 호출해서 사용하겠다는 의미이다.


firebaseHosting 폴더 하위의 index.js 파일도 다음과 같이 수정한다.

const functions = require('firebase-functions');

const express = require("express")

const app1 = express();

app1.get("/hello", (request, response) => {
  response.send("Hello from Express on Firebase!")
})

const api1 = functions.https.onRequest(app1)

module.exports = {
  api1
}

index.js

첫 줄을 제외하고 모두 추가한 코드이다.

express에서 제공하는 방식으로 코드를 작성하고,

functions 함수를 이용하여 생성한 api1을 firebase.json 설정으로 넘기는 코드이다.

Firebase 호스팅에서는 express를 Firebase functions을 통하여 구현한다.


express가 로컬에 글로벌로 설치된 경우에는 별도 설치를 하지 않아도 functions폴더 하위의 node_modules에 추가 되어있다.

다만, npm으로 다시 설치하지 않으면 package.json 파일에 express 정보가 등록되어 있지않아 배포시 오류가 발생한다.

직접 package.json 파일에 express 정보를 등록하거나, npm install express --save로 설치해서 등록한다.


이상의 코드에서는 hello라는 URL을 추가했다.

웹 브라우저에서 http://localhost:5000/hello를 입력해서 그림과 같이 출력되면 제대로 설정된 것이다.



지금까지 Firebase 호스팅에서 express를 사용하는 방법을 정리하였고,

이번에는 앞서 작성한 게시판 예제(board2)를 추가해서 구현한다.

app.js에서 했던 것처럼 index.js 파일에 board.js 파일을 가져오기 하도록 설정하고,

ejs도 사용한다고 설정해 주어야 한다.

const functions = require('firebase-functions');
const express = require("express")
const app1 = express();

app1.set('view engine', 'ejs');
app1.engine('ejs', require('ejs').__express);

app1.get("/hello", (request, response) => {
  response.send("Hello from Express on Firebase!")
})

app1.use('/board2', require('./board2'));

const api1 = functions.https.onRequest(app1)

module.exports = {
  api1
}

index.js

다음으로 ejs 라이브러리를 설치 한다.

앞서의 예제들은 HTML 처리 부분을 모두 ejs로 구현하였다.

일반적인 Node.js에서는 express를 설치하면 별도로 ejs를 설치하지 않아도 되지만,

Firebase 호스팅을 사용하기 위한 Node.js에서는 다음과 같이 별도로 설치해야 한다.

단, functions 폴더로 이동해서 실행해야 한다 (이유는 앞서 정리).

> cd functions

> npm install ejs --save


이외에도 board2.js에서 추가로 사용한 dateformat 라이브러리를 설치해야 한다.

> npm install dateformat --save


functions폴더 하위에 view 폴더를 생성하고,

앞서 작성한 예제(firebaseExample)의 view 폴더 하위의 board2 폴더를 복사해서 넣어준다.

route폴더의 board2.js 파일도 functions 폴더에 복사한다.

로그인 기능이 있는 board3을 복사해도 되지만, 조금더 단순한 board2을 배포한다.


다음으로 Firebase Admin SDK를 설치한다.

> npm install firebase-admin --save

지금까지 Firestore를 웹으로 접근하는 방법을 사용했지만,

Firestore 문서에서는 Node.js일 경우 Firebase Admin을 이용하여 접속하도록 하고 있다.

주의: 설치시 반드시 --save 옵션으로 package.json에 설치된 라이브러리 정보가 저장되도록 해야 한다.

그렇지 않으면 배포(deploy)시 정보를 찾을 수 없다는 오류가 발생한다.


접속 및 초기화 하는 여러가지 방법을 제공하는데, Cloud 함수에서 초기화하는 다음 코드를 이용한다.

const admin = require('firebase-admin');
const functions = require('firebase-functions');

admin.initializeApp(functions.config().firebase);

var db = admin.firestore();


이 코드를 board2.js에 다음과 같이 추가한다.

var express = require('express');
var router = express.Router();
var dateFormat = require('dateformat');

const admin = require('firebase-admin');
const functions = require('firebase-functions');

admin.initializeApp(functions.config().firebase);

var db = admin.firestore();

router.get('/', function(req, res, next) {
    res.redirect('boardList');
});

router.get('/boardList', function(req, res, next) {

    ~~ 생략 ~~

board2.js

apiKey등 기존의 접속 정보를 사용하지 않는다.

이미 로컬에서 firebase login인으로 Firebase 서버에 접속이 되어 있고,

firebase init로 폴더를 프로젝트로 초기화 할때 기본적인 설정이 모두 되어 있기 때문이다.


firebase serve로 서버를 재가동하고,

웹 브라우저에서 http://localhost:5000/board2/boardList로 접속해서 잘 동작하는 지 글쓰기나 수정작업을 해 본다.


서버를 중지하고, firebase deploy로 Firebase Hosting 서버에 배포하도록 실행한다.


이상의 그림과 같이 eslint가 찾아낸 6개의 경고와 6개의 오류가 발생하면서 배포가 중지된다.

Eslint는 코딩 스타일을 점검하는 라이브러리로,

인터넷에 자료가 많으니 자세한 사용법은 검색해 보길 바라고, 여기에서는 간단하게 정리하고 넘어간다.


화면 중앙의 빨간색 메시지 위가 오류와 경고에 대한 정보를 나타내는 것으로

숫자: 숫자는 오류가 발생한 행과 열의 번호를 의미하고

다음의 단어는 오류(error)인지 경고(warning)를 의미한다.

그 다음의 문자열은 오류에 대한 구체적인 메시지를 나타내고

마지막 문자열(예: prefer-arrow-callback)은 오류의 이름 (규칙명)을 의미한다.

사실 Eslint는 코딩 스타일을 규정하는 것으로 실제 오류가 발생한 것은 아니기 때문에 앞서서 개발할 때에는 별 문제가 없었다.

Firebase deploy시에는 코딩 스타일 준수를 요구하는데,

오류(error)라기 보다는 규칙(rule) 위반으로 심각한 문제를 발생시킬 수 있다는 의미이다.


6개의 경고는 빨간색 메시지(6 warnings potentially fixable with the `--fix` option)처럼 Eslint 실행시 –fix 옵션을 주면 자동으로 수정된다.

자세히 살펴보면, 6개의 경고가 발생했지만

경고 종류는 하나로 콜백 함수보다는 화살표 함수 표현(arrow function expression)을 사용하라는 의미이다.

기존: router.get('/boardList', function(req, res, next) {

수정: router.get('/boardList', (req, res, next) => {

경고는 수정하지 않아도 Firebase에서 배포가 되므로 수정하지 않고 넘어간다.


경고를 수정하고 싶은 경우나 다시 오류를 확인하고 싶은 경우 firebase deploy를 실행시키기 보다는 eslint를 직접 실행하는 것이 좋다.

eslint는  functions 폴더 하위의 node_modules\.bin 폴더에 명령어가 설치되어 있다.

따라서 현재 폴더가 functions 일 경우, 다음과 같은 문장으로 문제점을 수정할 수 있다.

          node_modules\.bin\eslint ../../board2.js --fix



6개의 오류지만 오류로 지정된 행을 소스 코드에서 찾아보면 3가지 오류인 것을 알 수있다.

1. then()을 사용한 경우 마지막에 return을 사용하거나 throw을 사용: promise/always-return

2. (then() 다음에) catch() 사용: promise/catch-or-return

3. doc 변수 선언이 두 번 되었음: no-redeclare


.eslintrc.json 파일을 열어서 주어진 규칙명으로 규칙을 경고(1)나 사용하지 않음(off 또는 주석처리) 표시해도 된다.

여기에서는 다음과 같이 프로그램 코드를(빨간색) 수정하였다.

router.get('/boardList', function(req, res, next) {
    db.collection('board').orderBy("brddate", "desc").get()
        .then((snapshot) => {
            var rows = [];
            snapshot.forEach((doc) => {
                var childData = doc.data();
                childData.brddate = dateFormat(childData.brddate, "yyyy-mm-dd");
                rows.push(childData);
            });
            res.render('board2/boardList', {rows: rows});
            return;
        })
        .catch((err) => {
            console.log('Error getting documents', err);
        });
});

router.get('/boardRead', function(req, res, next) {
    db.collection('board').doc(req.query.brdno).get()
        .then((doc) => {
            var childData = doc.data();
           
            childData.brddate = dateFormat(childData.brddate, "yyyy-mm-dd hh:mm");
            res.render('board2/boardRead', {row: childData});
            return;
        })
        .catch((err) => {
            console.log('Error getting documents', err);
        });
});

router.get('/boardForm', function(req,res,next){
    if (!req.query.brdno) { // new
        res.render('board2/boardForm', {row: ""});
        return;
    }
   
    // update
    db.collection('board').doc(req.query.brdno).get()
        .then((doc) => {
            var childData = doc.data();
            res.render('board2/boardForm', {row: childData});
            return;
        })
        .catch((err) => {
             console.log('Error getting documents', err);
        });
});

router.post('/boardSave', function(req,res,next){
    var postData = JSON.parse( JSON.stringify(req.body));
    var doc = null;
    if (!postData.brdno) {  // new
        postData.brddate = Date.now();
        doc = db.collection("board").doc();
        postData.brdno = doc.id;
        doc.set(postData);
    } else {                // update
        doc = db.collection("board").doc(postData.brdno);
        doc.update(postData);
    }
   
    res.redirect('boardList');
});

board2.js

추가적으로 Cloud Firestore 게시판 예제에서 정리(하단의 주의 참고)하였지만, 

Firebase 호스팅은 Node.js 6 을 지원하기 때문에 사용자가 작성한 데이터(req.body)를 그대로 저장(set, update)하면 오류가 발생한다.

따라서, 위 코드의 파란색 코드처럼 Json 변환을 해주어야 한다.


다시 배포를 진행해서 "Deploy complete"가 출력되는지 확인한다.

경고가 있어도 배포는 잘 이루어진다.


Firebase 콘솔의 Hosting 메뉴에서 배포한 기록이 등록되었는지 확인한다.

배포가 잘 이루어진 경우 모든 배포 기록이 등록되어 있다.



웹 브라우저에서 firebase 호스팅에서 제공하는 도메인으로 접속해 본다.

https://fir-example-ec491.firebaseapp.com/board2/boardList











AngularJS는 자바스크립트 기반의 오픈 소스 프론트엔드 웹 애플리케이션 프레임워크의 하나로,

싱글 페이지 애플리케이션 개발 중에 마주치는 여러 문제들을 해결하기 위해 개발되었다 (위키).

AngularJS의 기본 문법들은 간단해서 쉽게 익힐 수 있지만, MVC와 MVVM이 익숙하지 않으면 시작하기 어려운 단점이 있다.

더욱이 Angular CLI는 MVC(MVVM) 개념이 없으면 시작하기 어렵다.

여기에서는 간단한 게시판(CRUD)을 만들면서

AngularJS의 기본 문법을 익히고,

Angular CLI로 예제를 확장하면서 Angular의 특징을 익힐 수 있도록 정리하였다.

    1.  AngularJS 기반 게시판
    2.  Angular CLI 기반 게시판
    3.  Mongoose 연동 (준비중)


여기서는 NodeJS와 AngularJS 문법에 대한 설명을 정리하지 않는다.

몰라도 문제는 없을 것으로 여겨지지만, 조금은 알고 있는 것이 도움이 될 것이다.


먼저, AngularJS의 기본 예제를 작성하면서 문법을 익힌다.

AngularJS의 기본 예제는 W3School에 잘 정리되어 있다.

영어를 몰라도 왼쪽의 메뉴를 클릭해서 예제를 보다 보면 눈치로 기본 문법을 익힐 수 있다 (책을 읽는 것이 가장 좋지만… ).

첫 예제는 W3School의 예제로 입력상자(Text)에서 이름을 입력하면

이름에 ‘Hello’를 붙여서 하단에 출력하는 예제 (위 그림 하단의 코드)이다.

[Try it Yourself] 버튼을 눌러서 실행해 볼 수 있다.

먼저, AngularJS를 사용하기 위해 JS 파일을 포함한다 [라인3].

그리고, AngularJS 사용영역이라고 ng-app 지시자를 이용하여 표시한다 [라인6].

ng-app 지시자의 하위 (자식) 테그(Tag)는 모두 AngularJS로 제어할 수 있다.

입력상자에서 ng-model 지시자로 name이라는 이름을 주고 [라인7],

입력상자에서 사용자가 입력한 값을 {{}} 기호로 출력한다 [라인8].


기존의 JavaScript(JS) 방식으로 설명하면

입력상자의 이름을 name으로 지정하고 (ng-model),

이 name이라는 이름으로 입력상자의 값을 가져와 사용하는 예제이다.

AngularJS에서는 별다른 JS 코드 없이 몇 가지 지정만으로 사용자가 입력한 글이 아래에 출력된다.

ng-app, ng-model, {{}} 를 기억해 두면 된다.


다음 예제는 JS (AngularJS)에서 변수(hello)를 선언하고, 변수(hello)의 값을 HTML에서 {{}}로 출력하는 예제이다.

ng-app의 이름을 myApp으로 지정하고

컨트롤(ng-controller)를 myCtrl로 지정하여 제어하는 예제이다.

대부분의 코드가 컨트롤에서 작성된다.


HTML에서 앱 이름(myApp)과 컨트롤러 이름(myCtrl)을 부여하였다 [라인 18].

이 이름은 자바 스크립트에서 Angular JS가 사용하고 있다.

AngularJS 앱을 지정하고 [라인 9],

앱의 컨트롤러 [라인 11]을 지정하여,

변수 hello에 “Hello World”라는 문자열을 지정하였다 [라인 12].

모든 변수는 $scope 하위에 선언하여 사용한다 [라인 12].

변수의 값은 HTML에서 {{변수}}로 출력한다 [라인 19].

이 구조가 AngularJS의 가장 흔한 기본 구조이니 기억해야 한다.


다음으로 구현할 예제는 다음 그림과 같이 배열의 값을 게시판 리스트 출력하듯이 화면에 출력하는 예제이다.

게시판을 만들기 위해서는

서버에서 Oracle이나 MariaDB 같은 DBMS를 이용하여 저장하지만,

여기서는 AngularJS의개념을 익히기 위하여 간단하게 구현한 것이라

데이터 베이스에 데이터를 저장하도록 처리하지 않고, 배열에 저장하여 구현하였다.

데이터를 저장하기 위해 필드는 JSon으로, 행은 배열([])로 작성하였다 [라인 13~16].

필드는 id, title, writer의 3가지로 구성하였고, 2개의 행을 생성해서 boardlist라는 이름으로 지정하였다.


배열의 값은 ng-repeat 지시자(반복문)로 한번에 모두 출력한다 [라인 28].

ng-repeat 지시자를 TR 태그에 사용하였기 때문에

배열의 개수인 두 개의 TR 태그가 생성된다.

각 행의 값은 ng-repeat에서 지정한 item 변수에 넣어지고

item 변수의 키값(id, title, writer)를 추출해서 값을 출력한다 [라인 30, 31].

$index는 AngularJS에서 제공하는 변수로

현재 출력하는 배열의 위치(인덱스) 값을 제공한다 [라인 29].


CRUD 게시판을 구현하기 위해 이번에는 그림과 같이 한 행을 삭제하는 예제를 제작한다.

기존 코드에 다음 코드와 같이 remove 함수를 추가해서 구현한다.

app.controller("myCtrl", function($scope) {
    $scope.boardlist = [
        {id: 1, title: 'Title1', writer: 'Writer1'},
        {id: 2, title: 'Title2', writer: 'Writer2'}
    ];
   
    $scope.remove = function (id) {
        if (!id) return;

        var idx = -1;
        for (var i = 0; i < $scope.boardlist.length; i++) {
            if ($scope.boardlist[i].id === id) {
                idx = i;
                break;
            }
        }

        if (idx === -1) return;
        $scope.boardlist.splice(idx, 1);
    }
});
</script>    
</head>
<body>
    <div ng-app="myApp" ng-controller="myCtrl">
        ~~ 생략 ~~
                <td style="width: 50px"><button type="button" ng-click="remove(item.id)">삭제</button></td>
        ~~ 생략 ~~



실행해 보기    전체코드


앞서의 그림처럼 테이블 테그에 열(td)을 추가해서 버튼 태그을 생성하고, 해당 버튼을 클릭하면 remove() 함수를 호출하다.

함수 호출시 파라미터로 행의 키 값인 id 값을 생성한다.

id 값은 각 행의 고유 값(Primary key)을 나타내는 필드로 사용하였다.

일반적인 Click 이벤트 연결은 onclick=”remove(1)”처럼 구현하지만, AngularJS의 컨트롤과 연동하기 위해 ng-click 지시자를 사용한다.

즉, 이 지시자를 이용하여 HTML에서 AngularJS에 있는 함수를 호출할 수 있다.


변수를 $scope 하위에 boardlist로 사용하듯이

함수도 $scope 하위에 선언해서 사용한다 $scope.remove = function (id).


삭제하는 방법은 간단하다.

주어진 id값을 데이터(boardlist)에서 찾아서 지우면 된다.

지정된 행을 찾기 위해서는 모든 데이터들을(boardlist) 처음부터 행(length)의 개수만큼 반복해서 (for)

If문으로 값을 비교하고 if ($scope.boardlist[i].id === id),

id값이 일치하면 그 위치의 행을 삭제한다 .

배열을 삭제하는 것이기 때문에 splice() 함수를 이용한다.


주어진 id의 행을 찾는 것은 for문 대신에 다음과 같이 배열의 findIndex 함수를 사용하여 쉽게 구현할 수도 있다.

IE 하위버전에서 사용할 수 없다.

        var idx = $scope.boardlist.findIndex(function (item) {
            return item.id === id;
        });


이상의 삭제 예제에서 알아야 할 것은,

AngularJS에서 함수를 생성하는 방법과 어떻게 HTML에서 AngularJS의 함수를 호출하는 가이다.

$scope와 ng-click 을 기억해야 한다.


이번에는 그림과 같이 사용자가 입력한 값을 추가하는 기능을 구현한다.

기존 코드에 다음 코드와 같이 addBoard함수와 Form 테그 등을 추가해서 구현한다.

    $scope.boardForm = {};

    $scope.addBoard = function () {
        var newId = ! $scope.boardlist.length ? 1 : $scope.boardlist[$scope.boardlist.length - 1].id + 1;
        var newItem = {
            id: newId,
            title: $scope.boardForm.title,
            writer: $scope.boardForm.writer
        };

        $scope.boardlist.push(newItem);
    }
});
</script>    
</head>
<body>
    <div ng-app="myApp" ng-controller="myCtrl">
        <form ng-submit="addBoard()">
            Title: <input type="text" ng-model="boardForm.title" autofocus> <br/>
            Name: <input type="text" ng-model="boardForm.writer"><br/>
            <button type="submit">Add</button>
        </form>
       

실행해 보기    전체코드


제목(boardForm.title)과 이름(boardForm.writer)을 입력 받을 수 있도록 HTML 입력상자(Text)를 생성하고,

ng-model로 각각 이름을 boardForm.title과 boardForm.writer로 지정하였다.

boardForm라는 Json 형식의 변수를 AngularJS에서 선언하고, 구성 멤버를 id, title, writer로 지정하여 사용한다.

이 구성 멤버를 HTML에서 이름(boardForm.title, boardForm.writer)으로 바인딩시켜,

사용자가 값을 입력하면 자동으로 AngularJS의 boardForm 변수에 저장된다.

사용자가 저장 버튼을 클릭하면(submit), AngularJS의 addBoard함수를 호출하여, 새로운 행(Json)을 생성하여 배열에 넣어준다 (push).

HTML 입력상자(text)와 Angular 변수를 연결해서(Binding) 사용하는 개념을 이해해야 한다.

이번 예제의 핵심 개념이다.

추가하기 전에 각 행을 구별하는 키 필드인 id 값을 계산해야 한다.

id 값이 순서대로 정렬되어 있다는 전제로,

마지막 id 값이 가장 크다고 여겨서 마지막 id 값에 +1 한 값을 새로운 행의 id 값이 되게 구성하였다.

이 ID 값으로 삭제를 구현하였다.

이 ID 값을 이용하여 구현하지 않은 수정기능을 각자 구현해 보길 바란다.


이상으로 배열과 AngularJS로 간단하게 CRUD 게시판을 작성하였다.

AngularJS가 쉽고 재미난 프레임워크라는 것을 알 수 있다.

다음으로 AngularJS CLI를 정리한다.

AngularJS CLI로 가기 전에 앞서의 예제에서 구현하지 않은 수정 기능을 구현해 보는 것이 도움이 될 것 같다.


'Node.js > Angular JS' 카테고리의 다른 글

2. Angular CLI 기반 게시판  (4) 2018.02.28

Angular CLI(Command Line Interface)는 AngularJS와 비슷한 것 같지만 제법 많은 차이가 있다.

AngularJS는 JavaScript 기반으로 프론트 처리에 중점이 있고,

Angular CLI는 TypeScript 기반으로 백단(서버) 처리에 중점을 두고 있다.

(AngularJS는 1.x 버전을, Angular는 2.0 이후 버전을 의미한다.)

따라서 Angular CLI는 NodeJS 기반에서 운영되고 사용법이 제법 복잡하다.

더욱이 Angular CLI는 MVC(MVVM) 개념이 없으면, 많은 파일에 헷갈려서(?) 시작하기 어렵다.

간단한 게시판(CRUD)을 만들면서 Angular CLI의 개념을 익힐 수 있도록 정리하였다.

    1.  AngularJS 기반 게시판
    2.  Angular CLI 기반 게시판
    3.  Mongoose 연동 (준비중)


여기서는 NodeJS와 AngularJS 문법에 대한 설명을 정리하지 않는다.

몰라도 문제는 없을 것으로 여겨지지만, NodeJS와 Angular CLI 설치법은 알고 있어야 한다.

Angular CLI에 대한 사용법은 Angular CLI 공식 홈페이지에 예제로 잘 설명되어 있다.

영어가 부담스러우면 구글에서 한글 검색으로 angular heroes를 검색하면 된다.


여기서는 Angular의 Heroes 예제를 나름의 방식으로 쉽게 정리하였다.


먼저, NodeJS를 설치한 후에, 다음과 같이 npm을 이용하여 Angular CLI를 설치한다.

npm install -g @angular/cli


다음 그림과 같이 ng version으로 설치한 Angular CLI의 버전을 확인 할 수 있다.

Angular 5.2.5, Angular CLI 1.6.7이 설치되어 있다.


이하의 예제는 GITHUB에서 받을 수 있다.

다음 명령어를 실행하여 예제를 확인할 수 있다.

- git clone https://github.com/gujc71/angularBoard.git

- npm install

- ng serve


Angular CLI가 설치 되었으면,

Angular CLI로 angular-board 프로젝트를 생성한다.

ng new angular-board


cd angular-board를 실행하여 생성한 angular-board 폴더로 변경한다.

다음 명령어로 Angular CLI에서 제공되는 웹 서버를 실행한다.

Angular CLI는 nodeJS 기반이라 npm start로 시작해도 되지만 다음 명령어로 쉽게 실행할 수 있다.

ng serve


웹 브라우저에 http://localhost:4200/를 입력해서 다음 그림과 같이 실행되면, Angular CLI를 사용할 준비가 된 것이다.

IE 11 이하에서는 작동하지 않으니, Edge 이상을 사용해야 하고,

개발을 쉽게 하기 위해서는 Angular를 위해서 다양한 기능을 제공하는 Chrome을 사용하는 것이 좋다.


탐색기로 angular-board 폴더를 열어 보면, 매우 많은 파일들이 생성되어 있는 것을 볼 수 있다.

이중에서 src폴더 하위의 app 폴더에 있는 3개의 파일이,

이상의 그림과 같이 웹을 실행하여 사용자 눈에 보이는 데에 사용되는 파일이다.

app.component.html이 HTML을 작성하는 파일이고,

app.component.css가 CSS를 작성하는 파일이다.

대부분 웹 개발에서 이 두 파일을 구분하여 사용한다.

다만, app.component.ts는 TypeScript로 Anguler 프로그램을 작성하는 파일로

앞서의 AngulerJS 예제에서 작성한 JavaScript 코드를 여기에 작성한다고 보면 된다.

Java나 닷넷으로 치면 컨트롤 코드를 작성하는 파일이다.


각 파일에 기본으로 작성된 코드는 각각 다음과 같다.

app.component.css는 빈 파일로 CSS를 사용하지 않았다.


app.component.html

app.component.html 파일에는 앞서의 시작화면을 보여주기 위한 HTML코드가 작성되어 있다.

Anguler 로고를 출력하기 위해 img 태그가 있고 [라인 5],

3개의 링크(href)를 사용하였다.

AngulerJS와 동일하게 {{}}를 이용하여 title이라는 변수의 값을 출력한다[라인 3].


app.component.ts

app.component.ts의 내용은 일반적인 Anguler 기본 구조 코드로,

Anguler에서는 3개의 파일이 한 세트이고,

ts 파일에서 사용할 HTML 파일(templateUrl)과 CSS 파일(styleUrls)을 지정한다 [라인 5,6].

selector로 HTML에서 Anguler가 사용될 앱 영역을 지정한다 [라인4].

기본 클래스(AppComponent)에서 title 변수에 app라는 문자열을 지정하여 HTML에서 출력하게 된다.

위 그림의 Welcome to App이 이렇게 출력된 것으로 여기에서 값을 수정하면 바뀌는 것을 확인 할 수 있다.

AngulerJS에서는 $scope의 하위에 변수나 함수를 추가해서 사용했지만,

Angular CLI에서는 객체지향 프로그램처럼 클래스(class)를 이용하여 구현한다 [라인8].


title 변수의 값을 'app'에서 'Anguler'로 바꾸어 저장하면,

자동으로 webpack이 컴파일을 실행하고 웹브라우저의 실행 결과가 바뀌어 있는 것을 볼 수 있다.



이제부터 게시판을 구현하기 위한 본격적인 Angular 프로그램을 작성한다.

먼저, 다음과 같이 board 컴포넌트를 생성한다.

컴포넌트는 애플리케이션의 화면을 구성하는 뷰(View)를 생성하고 관리하는 것이다.

즉, 앞서 정리한 3개 파일(html, css, ts)을 의미한다.

기본 컴포넌트로 개발해도 되지만, Angular 문서에서는 별도의 컴포넌트를 생성해서 사용하는 것을 권장한다.

ng generate component board


다음과 같이 3개의 파일이 생성된다.

(board.component.spec.ts 파일도 있지만 여기서 정리하지 않는다.)

이 파일들을 직접 생성해도 되지만 generate component를 이용해서 한번에 쉽게 생성할 수 있다.


board.component.ts의 내용은

app.component.ts와 비슷한 것 같지만 기본 클래스가 좀더 복잡하게 구성되어 있다.

그리고, board 컴포넌트의 기본 selector로 app-board가 지정되어 있다.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-board',
  templateUrl: './board.component.html',
  styleUrls: ['./board.component.css']
})
export class BoardComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

board.component.ts

board.component.htm 파일에는 한 문장을 출력하도록 작성되어 있다.

<p>
  board works!
</p>

board.component.html

빈 CSS 파일을 포함해서 3개의 파일이 생성되었다.

웹 브라우저로 실행시킨 결과는 변한 것 없이 동일하게 Angular 로고가 출력된다.


app.component.html 파일 내용을 모두 지우고, 다음 코드와 같이 app-board 태그를 추가한다.

앞서 board.component.ts에서 selector로 지정한 값이다.

<app-board></app-board>

app.component.html

다음 그림과 같이 기존 내용이 사라지고 board works!가 출력된 것을 확인할 수 있다.

기본 app 컴포넌트 대신에 추가한 board 컴포넌트의 내용이 실행된 것이다.


이제부터 게시판 코드를 board.component.html와 board.component.ts에 작성하여 예제를 개발하면 된다.

앞서 AngularJS에서 작성한 마지막 예제(sample4.html)

board.component.html와 board.component.ts에 적절하게 나누어 작성한다.

파일 수정과 동시에 다름 그림과 같이 실행되는 것을 볼 수 있다.


먼저, board.component.html 파일의 내용을 다음과 같이 작성한다.

기본 코드는 AngularJS로 만든 마지막 예제(sample4.html의 HTML 부분과 동일하다.

board.component.html

AngularJS의

ng-model 대신에 [(ngModel)]을 사용하고,

ng-click 대신에 (click)을,

ng-repeat 대신에 *ngFor를 사용한다.

board.component.ts

board.component.js 파일도 JavaScrpt부분과 거의 유사하게 작성하였다.

변수 선언과 함수 사용도 거의 비슷하다.

근본적인 차이는 클래스 사용에 있다.

AngularJS는 JavaScript 기반이라 함수 기반으로 구성되었고,

Angular CLI는 TypeScript 기반으로 보다 객체 지향적으로 제작한다.

코드에서 보듯이 클래스(class) 구조로 작성한다.


AngularJS에서는 Json으로 사용한 boardForm이

Angular CLI에서는 BoardVO 클래스 변수로 사용된다.

Boardlist도 동일하다.

Angular CLI에서는 클래스 개념을 강조하여 MVC(MVVM)처럼 사용하여, Java(Spring, Struts)의 MVC와 거의 흡사하게 사용한다.


이상의 코드만 작성해서 실행하면 그림과 같은 오류가 발생한다.

웹 브라우저에서는 아무 것도 나오지 않기 때문에 오류 발생을 알 수 없고, 크롬(Chrome) 개발자 도구(F12)의 [Console] 탭에서 확인할 수 있다.

ng serve를 실행한 뒤, cmd 창(콘솔창)에 오류 메시지가 출력되기도 하지만

대부분의 오류는 크롬의 콘솔창에서 확인할 수 있다.

디버깅도 크롬의 sources 창에서 할 수 있으니 찾아보길 바란다.


HTML의 값을 가지고 오기 위해 사용하는 [(ngModel)]를 사용하기 위해서는

다음과 같이 빨간색으로 표시한 코드를 app.module.ts에 추가해야 한다.

Angular에서 제공하는 다양한 입력(폼) 기능을 사용한다는 의미이다.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule }   from '@angular/forms';

import { AppComponent } from './app.component';
import { BoardComponent } from './board/board.component';


@NgModule({
  declarations: [
    AppComponent,
    TestComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.module.ts

코드를 추가할 때는 코드 수정시 webpack이 즉시 컴파일해서 웹 브라우저에서 바로 확인이 가능했지만,

위 수정을 한 경우 서버를 재가동해야 하는 경우가 있다.

개발 서버를 종료하고 다시 실행한다 (ng serve).


이상으로 단순한 CRUD 기능을 게시판으로 구현해 보았다.

위 코드를 모두 따라해서 작성해 볼 수 있고, GitHub에서 다운로드 받아서 확인할 수 있다.

GitHub의 step1 branch가 이상의 예제에 대한 코드이다.


마지막으로 앞서의 코드를 정리하여, MVC(MVVM)처럼 구현한다.

이 구현을 위해 두가지를 수정한다.


먼저, BoardVO 클래스를 컴포넌트 파일 내에 같이 작성했지만 BoardVO.ts 파일로 따로 작성한다.

Angular CLI 공식 문서에는 Hero라는 클래스를 별도의 ts 파일로 작성해서 사용한다.

src\app 폴더에 BoardVO.ts파일로 다음과 같이 생성한다.

export class BoardVO {
  id: number;
  title: string;
  writer: string;
}

BoardVO.ts

같은 파일 내에서는 사용하지 않았지만,

다른 파일일 경우에는 외부에서 사용한다는 의미로 class 앞에 export를 지정한다.


이렇게 외부로 뺀 클래스(BoardVO)는

다음과 같이 BoardVO 클래스 코드를 지우고 import 로 가져온다.

import { Component, OnInit } from '@angular/core';
import { BoardVO } from '../BoardVO';

var BoardList: BoardVO[] = [
  { id: 1, title: 'Title 1', writer: 'Mr. Nice' }
];

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.css']
})
 ~~ 생략 ~~

board.component.ts

BoardVO.ts는 src\app 폴더에 있고

board.component.ts는 src\app\board 폴더에 있어서 상위 폴더를 나타내는 [../]가 사용한다.


다음으로, 서비스(service)를 구현한다.

MVC(Model–View–Controller, MVC)에서는 데이터와 관련된 처리나 반복된 기능은 서비스(service)에 구현하는 것이 추천된다.

서비스 사용법은 Angular CLI 예제에 잘 설명되어 있다.

여기서는 데이터 관련 처리를 DBMS에서 하지 않고, 배열로 처리하였다.

따라서, 배열 처리 부분을 모두 서비스에 작성한다.


서비스 파일을 작성하기 위해,

콘솔창(cmd)에서 ng generate service 명령어로 다음과 같이 board 서비스를 생성한다.

ng generate service board



src/app 폴더에 board.service.ts 파일이 생성된 것을 확인 할 수 있다.

생성된 소스는 다음과 같다.

import { Injectable } from '@angular/core';

@Injectable()
export class BoardService {

  constructor() { }

}

board.service.ts

generate service 지시어로 서비스 파일을 생성하고,

환경 설정까지 잘 이루어 지지만, 간혹 설정 문제로 오류가 발생할 수 있다.

app.module.ts 파일에 다음과 같이 코드가 추가되었는지 확인해야 한다.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule }   from '@angular/forms';

import { AppComponent } from './app.component';
import { BoardComponent } from './board/board.component';
import { BoardService } from './board.service';

@NgModule({
  declarations: [
    AppComponent,
    BoardComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [BoardService],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.module.ts

이상으로 서비스를 사용할 준비가 되었다.

서비스 파일(board.service.ts)에 구현할 코드는 컴포넌트에 있는 코드를 모두 복사해서 사용한다.

단순 기능만 구현했기 때문에 컴포넌트에 있는 코드 모두가 데이터 관련 코드이기 때문에

컴포넌트(board.component.ts)에 있는 코드를 다음과 같이 복사해서 작성한다.

import { Injectable } from '@angular/core';

import { BoardVO } from './BoardVO';

var BoardList: BoardVO[] = [
  { id: 1, title: 'Title 1', writer: 'Mr. Nice' },
  { id: 2, title: 'Title 2', writer: 'Narco' }
];

@Injectable()
export class BoardService {

  constructor() { }

    boardlist = BoardList;

    getBoardList(): BoardVO[] {
        return this.boardlist;
    }
   
      removeBoard(id: number):void {
        var idx = this.boardlist.findIndex(function (item) {
            return item.id === id;
        });
        if (idx === -1) return;
        this.boardlist.splice(idx, 1);
    }
   
    addBoard(boardForm: BoardVO): void {
        var newId = ! this.boardlist.length ? 1 : this.boardlist[this.boardlist.length - 1].id + 1;
       
        var newItem = {
            id: newId,
            title: boardForm.title,
            writer: boardForm.writer
        };
        this.boardlist.push(newItem);
    } 
}

board.service.ts

컴포넌트에서 ../BoardVO로 작성한 것을 ./BoardVO로 점(.)을 하나 빼고 작성했다.

컴포넌트(board.component.ts)는 src/app/board폴더에 있고,

BoardVO는 src/app에 있어서 상위 폴더(..)를 지정했지만

서비스(board.service.ts) 파일은 같은 폴더(.)에 있기 때문이다.


다음으로 getBoardList 함수가 추가 되었다.

단순하게 데이터를 저장한 배열(boardlist)을 반환한다.

컴포넌트에서는 컴포넌트 내부의 배열이라 변수를 그냥 호출해서 사용했지만,

서비스로 배열을 옮겼기 때문에 getBoardList() 함수를 이용하여 받아온다.


board.component.ts

컴포넌트의 boardlist가 getBoardList()로 대체되면서,

이 boardlist를 참조하는 board.component.html의 코드도 다음과 같이 바뀌어야 한다.

    <tr *ngFor="let item of getBoardList(); let inx = index">
        <td>{{inx+1}}</td>
        <td>{{item.title}}</td>
        <td>{{item.writer}}</td>
        <td><button type="button" (click)="removeBoard(item.id)">삭제</button></td>
    </tr>

board.component.html

이상으로 Angulr를 MVC처럼 구현해 보았다.

위 코드를 모두 따라해서 작성해 볼 수 있고, GitHub에서 다운로드 받아서 확인할 수 있다.

GitHub의 step2 branch가 해당 내용이다.


여기서 정리한 내용은 Angular 개념을 잡기 위한 기초 내용으로, Angular는 더 많은 기능을 제공한다.

Routing, HTTP등의 기능부터, 입력값 검사 등의 다양한 기능이 제공되니 찾아서 익혀 보길 바란다.


여기서는 구현하지 않았지만, 수정 기능도 구현해 보길 바란다.

Angular CLI 예제(heroes)에서는 입력기능 없이 수정 기능이 구현되어 있으니, 참고해서 구현해 보면 Angular를 이해하는 데 도움이 될 것이다.

Angular CLI 예제는 bootstrap을 사용해서 보기 좋게 개발하는 방법도 알 수 있다.





'Node.js > Angular JS' 카테고리의 다른 글

1. AngularJS 기반 게시판  (5) 2018.02.28

nineBatis (9batis)

Java에서 SQL문을 처리 할 때 많이 사용하는 Mybatis처럼,

Node.js에서 SQL을 쉽게 사용하기 위해 간단하게 제작한 라이브러리이다.


여기에서는 이전에 정리한 Node.js 기반의 단순 게시판

9batis를 적용하여

9batis의 사용법을 예제와 문서로 정리하였다.


게시판 예제는 Node.js (express)와 MySQL을 기반으로

단순 CRUD 기능을 구현한 예제이고,

board1.js, board2.js의 2가지 예제로 구성되어 있다.


이 예제에 9batis을 적용하여 3 가지 예제를 추가하였다.

9batis를 이용하여

SQL 문장만 반환 받아서 사용하는 단순 예제(board3.js),

SQL을 실행하고 결과를 반환 받아서 사용하는 기본 예제(board4.js),

중첩 SQL문 실행 등 다소 복잡한 기능을 정리한 예제(board5.js)로 구성하였다.


routes 폴더에 있는 각 컨트롤(board3.js, board4.js, board5.js)들은

각각의 HTML 파일(ejs)을

views 폴더 하위에

컨트롤과 동일한 이름의 폴더(board3, board4, board5)로 가지고 있다.

각 폴더에는 list.ejs, form.ejs, read.ejs 파일이 있다 [그림 왼쪽 참조].


이 샘플 코드는 github에서 받을 수 있다.

github 사용법은 게시판 예제에 정리되어 있으니 참고하면 되고

정리된 내용 중에서 다운 받을 github 주소를

boardJS에서 nineBatisSample로 바꾸어서 따라하면 된다.

https://github.com/gujc71/nineBatisSample


이제부터 nineBatis (9batis)의 사용법에 대해서 정리한다.


Node.js에서는 SQL문을 문자열로 처리하여

컨트롤과 같이 작성한다.

nineBatis (9batis)에서는 SQL문을 별도의 XML 파일로 작성하여 관리한다.

다음 코드(board3.xml)와 같이

select, insert 등의 태그를 사용하여 SQL 문을 작성하면 된다.

다음 코드는 게시판 예제(board1.js)에서 SQL 문만 따로 추출하여

9batis 문법에 맞추어 XML로 정리한 것이다.

board3.xml

XML 파일은 한개 이상을 사용할 수 있고

별도의 폴더에 (예제는 routes/sql) 모아서 사용해야 한다.


태그에 사용되는 id (selectBoardList, selectBoardOne 등)는

전체 XML 파일내에서 고유한 값이어야 한다.

중복된 id를 지정하면 Node.js 시작시 오류 메시지를 출력하고

Node.js 실행을 중지한다.


XML 파일에서 제공되는 기본 기능(명령어)은 4 가지이다.

숫자 변수, 문자 변수, include, if문이 지원된다.

이것은 Mybatis와 비슷하게 구현했는데

사용한 라이브러리의 제약(xml2js)등의 문제로 아직은 사용법이 다소 부족하다.


위 코드에서 사용한 것처럼

숫자 변수는 ${변수} [라인 4],

문자 변수는 #{변수} 로 사용한다 [라인 21, 27].

MyBatis에서는 #을 PrepareStatement로

$를 Statement로 정의하여 구분하지만,

여기서는 쉽게 정의하기 위해 문자와 숫자 변수로 정의하였다.


include, if문은 XML로 작성해야 하는데

xml2js 파싱시 처리가 되지 않아

{include ref='sql 태그 id'}로 사용한다 [라인 16, 22, 32].

include문은 위 SQL 문처럼 (includeWhere)

중복된 코드(sql 태그)를 별도로 지정하고 [라인 4],

이 것을 사용할 SQL에서 가져오고자 할 때 사용한다 [라인 16, 22, 32].

이때 ID가 일치해야 한다.


if 문은 다음 코드처럼

@{if test="조건식"}실행할 내용{/if}으로 사용한다.

정규식을 이용한 파서에 문제가 있어서 IF문에만 @를 사용했는데,

일관성을 위해 include도 @{include}로 처리할지 고려 중이다.


if 문은 board5.xml 파일에 다음과 같이 작성되어 있다.

board5.xml

조건식은

숫자 변수를 사용할 경우에는 $변수 > 10과 같이 사용하고

문자 변수를 사용할 경우에는 $변수 === "test"와 같이 사용한다.

Mybatis와 마찬가지로 else 문이 없다.

따라서 예제와 같이 두개의 if문을 사용해야 한다.


데이터를 정해진 개수(cnt) 만큼 가져올 때와 [라인 6]

정해진 개수가 없거나($cnt === undefined), 0 이면 [라인 11]

모든 데이터를 가져오도록 조건을 사용하지 않았다.


명령어

설명

예제

 #{변수명}

 문자열 변수 (PrepareStatement)

 #{cnt}

 ${변수명}

 숫자형 변수 (Statement)

 ${cnt}

 {include}

 공통 SQL 문 가져오기

 {include ref='sql 태그 id'}

 @{if test="조건식"}

 비교연산자

 @{if test="cnt>1"}실행할 내용{/if}


이렇게 작성한 SQL문을

nineBatis(9batis)를 이용하여 프로그램(js)에서 호출해서 사용한다.


9batis는 다음과 같이 선언하여 사용한다.

var batis9 = require("./nineBatis");
batis9.loadQuery(__dirname +'/sql', true);

9batis 파일(nineBatis.js)은

아직 npm에 등록하지 않아서 nineBatis.js 파일을 복사해서 사용해야 한다.

샘플 프로젝트의 routes 폴더에 다른 코드들과 같이 있다.

(등록은 어느 정도 쓸만하다 싶을 때 할 예정)


require로 nineBatis.js 파일을 가져오기 한 후,

9batis의 loadQuery() 함수로 xml 파일들을 읽어들인다.

파라미터로 xml파일들이 있는 폴더(sql)를 지정하고,

디버그 여부를 지정하면 된다.

디버그를 true로 지정하면 console에 현재 실행하는 SQL문이 출력된다.


Node.js를 Eclipse에서 실행한 경우에는 하단 console 탭에 SQL ID와 SQL문이 출력된다.

Node.js를 cmd 창에서 실행한 경우에는 cmd 창에 SQL ID와 SQL문이 출력된다.

SQL ID와 SQL문이 출력이 중요한 것이

ID로 문제가 되는 SQL을 쉽게 찾을 수 있고

출력된 SQL 문을 복사해서

전용 Client (WorkBench, HeidiSQL등)에서 붙여넣고 실행하면

쉽게 SQL 오류를 찾을 수 있기 때문이다.


loadQuery()는 한 프로젝트에서 한번만 실행해야 하고

커넥션 풀링처럼

9batis을 app.js에서 실행한 후

변수 batis9 을 글로벌로 사용하는 것이 좋다.


먼저, 9batis를 소극적으로 사용한 예제이다.

board3.js

예제로, 모든 데이터를 DB에서 가져와 출력하는

게시판 글 리스트 부분을 정리하면,

기존에는 다음과 같이 SQL문이 프로그램 내에서 문자열로 사용되었다.

router.get('/list', function(req,res,next){
    pool.getConnection(function (err, connection) {
        var sql = "SELECT BRDNO, BRDTITLE, BRDWRITER, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE" +
                   " FROM TBL_BOARD";
        connection.query(sql, function (err, rows) {
            if (err) console.error("err : " + err);

            res.render('board1/list', {rows: rows});
            connection.release();
        });
    });
});

board1.js

9batis에서는

이 SQL문을 XML (board3.xml)로 저장하고,

ID (selectBoardList)를 부여하여 작성한 뒤에

컨트롤에서는 이 ID로 호출하면 (batis9.getQuery())

해당 SQL문을 반환 받아서 사용하게 된다 [라인 15].

이것이 9batis의 기본 사용 방법이자 기본 개념이다.


조금 더 복잡한 기능으로

SQL문에 필요한 파라미터를 지정 방법에 대하여 정리한다.

기존에는 글읽기와 글저장 부분의 SQL문이

다음과 같이 문자열로 사용되었다.

router.get('/read', function(req,res,next){
    pool.getConnection(function (err, connection) {
        var sql = "SELECT BRDNO, BRDTITLE, BRDMEMO, BRDWRITER, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE"+
                   " FROM TBL_BOARD" +
                  " WHERE BRDNO=" + req.query.brdno;
        connection.query(sql, function (err, rows) {
            ~~ 생략 ~~
        });
    });
});

router.post('/save', function(req,res,next){
    var data = [req.body.brdtitle, req.body.brdmemo, req.body.brdwriter, req.body.brdno];

    pool.getConnection(function (err, connection) {
        var sql = "";
        if (req.body.brdno) {
            sql = "UPDATE TBL_BOARD" +
                       " SET BRDTITLE=?, BRDMEMO=?, BRDWRITER=?" +
                  " WHERE BRDNO=?";
        } else {
            sql = "INSERT INTO TBL_BOARD(BRDTITLE, BRDMEMO, BRDWRITER, BRDDATE) VALUES(?,?,?, NOW())";
        }
        connection.query(sql, data, function (err, rows) {
            ~~ 생략 ~~
        });
    });
});

board1.js

SQL문에 필요한 파라미터는

글 읽기(/read)를 예로 할 경우 글 번호(brdno)를 의미한다.

지정된 글 번호에 따라 DB에서 글 내용을 조회하여 화면에 출력한다.

이 글 번호는 글 리스트에서 사용자의 선택에 따라 바뀌는 것으로

위 코드처럼 지정된 글 번호(req.query.brdno)에

맞는 데이터(WHERE)를 가지고 오도록

문자열 조작을 통하여 SQL문을 작성 한다.

" WHERE BRDNO=" + req.query.brdno


글 저장(/save)은 사용자가 입력한 값을 저장하기 위한 것으로

파라미터를 모두 ? 로 지정하고

배열 (data 변수)로 값을 넘기는 방식으로 작성한다.


9batis에서는

getQuery()를 호출할 때, JSON으로 파라미터를 지정한다 [라인 27, 41, 43].

SQL문 작성시 ${brdno}로 지정한 경우,

JSON의 키 이름을 brdno 로 하여 사용한다 [라인 27].

9batis에서 해당 변수에 JSON 값을 치환하여 SQL문을 반환한다.

(콘솔에 출력된 SQL문으로 확인할 수 있다.)

$는 SQL 문 변환시 숫자라는 의미로 값만 출력되고,

#은 문자라는 의미로 홑따옴표(')로 싸여서 출력된다.

    <sql id="includeWhere">
        WHERE BRDNO=${brdno}
    </sql>
 
    <update id="updateBoard">
        UPDATE TBL_BOARD
           SET BRDTITLE=#{brdtitle}, BRDMEMO=#{brdmemo}, BRDWRITER=#{brdwriter}
          {include refid="includeWhere"}
    </update>
    
    <insert id="insertBoard">
        INSERT INTO TBL_BOARD(BRDTITLE, BRDMEMO, BRDWRITER, BRDDATE)
        VALUES (#{brdtitle}, #{brdmemo}, #{brdwriter}, NOW())
    </insert>

board3.xml

이렇게 구현하면서 부수적인 효과는

기존에는 Node.js에서 제공하는 JSON 데이터(req.body)를

배열 (data 변수)로 변환해서 사용했지만

  var data = [req.body.brdtitle, req.body.brdmemo, req.body.brdwriter, req.body.brdno];

9batis에서는 JSON 데이터(req.body)를 그대로 사용한다 [라인 41, 43].

  sql = batis9.getQuery('insertBoard', req.body);


이번에는 9batis를 제대로 사용하는 방법(board4.js)을 정리한다.

board4.js

일단, 일부만 올린 board3.js 파일의 내용 보다 확 줄어든 코드 양을 볼 수 있다.

이것은 DB에 접속해서 SQL을 실행하고

실행 결과를 반환 받는 부분을 execQuery() 함수로 처리했기 때문이다.


다음과 같이 글 리스트(/list)에 대하여

기존에 작성한 코드와

9batis로 작성한 코드를 직접 비교하면 쉽게 알 수 있다.

router.get('/list', function(req,res,next){
    pool.getConnection(function (err, connection) {
        var sql = "SELECT BRDNO, BRDTITLE, BRDWRITER, DATE_FORMAT(BRDDATE,'%Y-%m-%d') BRDDATE" +
                   " FROM TBL_BOARD";
        connection.query(sql, function (err, rows) {
            if (err) console.error("err : " + err);

            res.render('board1/list', {rows: rows});
            connection.release();
        });
    });
});

board1.js

execQuery() 함수를 호출하고

반환 받은 실행 결과인 rows을 render()에 파라미터로 지정하면 끝이다.

파라미터가 null인 것은 SQL문 생성을 위해 필요한 값이 없다는 것으로,

SQL문 생성을 위해 필요한 값이 있는 경우

앞서 정리한 예제와 동일하게 JSON 으로 지정하면 된다 [라인 19, 29, 37 43 참조].

router.get('/list', function(req,res,next){
    batis9.execQuery(pool, 'selectBoardList', null, function (rows) {
         res.render('board4/list', {rows: rows});
    });
});

board4.js

execQuery()의 파라미터는

DB접속 클래스, 실행할 SQL 아이디, SQL 실행에 필요한 파라미터(JSON), SQL 실행 완료 후 실행할 콜백 함수

의 4 가지 이다.

이 중 DB접속 클래스(pool)는

loadQuery() 호출시 미리 지정해서 사용하는 것이 편리하지만

이해를 위해 현재는 이렇게 구현했다.

메소드

설명

예제

 loadQuery

 - SQL문이 있는 XML 파일을 읽어서 보관.

 - 파라미터

   filepath: XML 파일이 있는 폴더
   mode: 디버깅 여부.

 batis9.loadQuery(__dirname +'/sql', true)

 getQuery

 - 지정된 ID의 SQL문을 찾아서 반환.

 - 파라미터

   id: XML 파일에서 지정된 SQL문의 id
   params: SQL문에 지정된 변수의 값 (Json)

 batis9.getQuery('selectBoardList');

 batis9.getQuery('selectBoardOne', {brdno: 1});

 execQuery

 - 지정된 ID의 SQL문을 찾아서 실행

 - 파라미터

   pool: DBMS에 대한 연결
   id: XML 파일에서 지정된 SQL문의 id
   params: SQL문에 지정된 변수의 값 (Json)
   callback: SQL문 실행후 호출할 함수

batis9.execQuery(pool, 'selectBoardList', null, function (rows) {
    res.render('board4/list', {rows: rows});
});


마지막으로 2개 이상의 SQL문을 실행하는 것과

하나의 SQL문으로 두 가지 기능(IF문)을 하는

다소 복잡해 보이는 예제(board5.js)를 정리한다.

board5.js

먼저, 9 라인 과 10 라인을 보면

두 개의 SQL문이(selectBoardOne, selectBoardList) 사용된 것을 볼 수 있다.


글 읽기(/read)에서

지정된 글 내용을 DB에서 가지고 온다(selectBoardOne).

이 기능에 이전 글에 대한 정보(selectBoardList5)를 제공하는 기능을 추가하였다.

현재 글을 모두 읽은 후

글 리스트로 돌아가서 다른 글을 선택하는 것이 아니라

글을 읽던 페이지에서 바로 이전 글을 읽을 수 있도록 하는 것이다.

(다음 글 기능은 각자 구현해 보길)


두 개의 SQL문을 사용하는 것은

기존의 Node.js에서 사용하는 것이랑 동일하게

하나의 SQL문이 실행 완료되면 [라인 9],

지정된 콜백 함수에서 나머지 SQL문을 실행하도록 하면 된다 [라인 10].

이 기능은 구현한 것이 아니고 Node.js의 기능을 그대로 쓰는 것이다.


다음으로 하나의 SQL문(board5.xml)으로 두 가지 기능을 하도록 구현한다.


글 읽기에서 이전 글에 대한 정보를 가지고 오는 것은

글 리스트와 거의 유사한 SQL문을 사용한다.

이전 글은 현재 글 보다 글 번호가 적은 글 중 하나를 가지고 오는 것이다.

글 리스트(/list)는 그냥 모든 글을 가지고 온다.


이 둘의 공통 점은 글에 대한 정보(글 제목, 작성일자 등)를 가지고 오는 것이고

차이점은

조건에 의한 하나의 행(LIMIT)을 조회하느냐,

또는 여러 개의 리스트를 가지고 오느냐 이다.


board5.xml

기존에 사용하던 selectBoardList를 두고

예제를 위해 별도로 selectBoardList5로 새로 생성하였고,

출력할 데이터 개수(cnt)를

지정하면(if) 이전 글을 찾는 SQL문을 [라인 6~10],

지정하지(else) 않으면

조건을 사용하지 않아 모든 데이터가 조회된다 [라인 11~13].


이 SQL문을

글 리스트(/list)에서는 별 지정 없이(null) 호출하고 [board5.js 라인 3]

batis9.execQuery(pool, 'selectBoardList5', null,


글 읽기(/read)에서는 {cnt: 1}과 같이 파라미터를 지정한다 [board5.js 라인 10].
    batis9.execQuery(pool, 'selectBoardList5', {brdno: req.query.brdno, cnt: 1},



이상으로 nineBatis (9batis)의 사용법을 정리하였다.

9batis는 SQL문을 XML 파일에 저장하고,

프로그램에서 간단하게 호출하여 (execQuery)

사용할 수 있도록 도와 주는 Node.js 라이브러리로

여기에서 다운로드 받을 수 있다.




Eclipse에서 Node.js (Nodeclipse)를 사용하는 방법을

다음과 같이 4가지로 정리하였다.

1. Node.js 프로젝트 생성 및 개발 준비

2. MariaDB 기반 CRUD 게시판 만들기

3. 게시판 예제 보강

4. GitHub에서 Node.js 소스 가져오기


Node.jsNodeclipse 설치는

인터넷을 찾아 보길 바라고, 여기서는 설치한 이후부터 정리한다.

Express 기반으로 제작하기 때문에

Express (express-generator 포함)도 설치해야 한다.


Node.js는 Atom이나 notepad++로 작성하고

콘솔에서 실행해서 개발해도 되지만,

디버깅과 사용의 편의성 때문에 Eclipse에서 작성하는 것을 선호한다.

(타이핑을 안 좋아해서 GitHub도 eclipse로 이용)

Nodeclipse은 eclipse에서 Node.js를 사용할 수 있게 해주는 플러그인으로

Eclipse의 marketplace에서 Nodeclipse로 검색해서 설치하면 된다.


개발을 위해 새로운 프로젝트(board)를 생성한다.

새 프로젝트는 Nodeclipse를 이용하여 생성할 수 있지만 (그림참조),

웹 개발에 필요한 기본 설정을 제대로 작성해주지 않기 때문에

express로 콘솔 창에서 프로젝트를 생성하고,

Eclipse에서 Import 해서 사용하는 방법을 이용한다.



먼저 console 창에서

다음과 같이 입력하여 사용할 프로젝트를 간단하게 생성한다.

board 만 원하는 이름으로 지정하면 된다.

> express --session --view=ejs --css css board

> cd board

> npm install



express-generator에서 웹 개발에 필요한 여러가지 설정을 해주기 때문에

위와 같이 콘솔 창에서 프로젝트를 생성하는 것이 더 좋다.


주의: 콘솔 창에서 express 명령어를 사용하려면 express-generator를 설치해야 한다.

> npm install express-generator -g


콘솔창에서 [npm start]를 입력하여 웹 서버를 실행한다.

웹 브라우저에서 [http://localhost:3000/]를 입력하여

다음 그림과 같이 실행되면,

제대로 Node.js용 웹 프로젝트가 생성된 것이다.


콘솔 창에서 실행한 Node.js를 중지하고,

Eclipse를 실행해서

콘솔창에서 생성한 프로젝트를 가져오기(import)한다.


Eclipse에서 File > Import 를 실행하고

"Projects from Folder or Archive"을 선택한다.


"Directory" 버튼을 클릭해서,

express(express-generator)로 생성한 프로젝트의 폴더를 지정하고,

"Finishi" 버튼을 클릭한다.


[Project Explorer] 뷰에서

가져오기 한 board 프로젝트의 파일들을 볼 수 있다.

node_modules 폴더에 빨간색 X 표시가 된 것은

몇몇 라이브러리가 제대로 설치되지 않은 경우 나타나는 것으로

예제 구현에 문제가 없으니 넘어간다.


bin 폴더 하위에 있는 www 파일을 선택하고

컨택스트 메뉴에서 Run As > Node Application을 실행한다.

앞서 콘솔 창에서 [npm start]를 실행한 것과 같이

eclipse에서 웹 사이트를 실행하는 방법이다.


다음 그림 하단의 콘솔(console) 탭에 보이는 것처럼

종료(Teminate) 버튼이 빨간색으로 나타나면

웹 사이트가 제대로 실행된 것이다.

웹 브라우저에서 [http://localhost:3000/]를 입력하여 접속을 확인한다.


bin 폴더 하위에 있는 www 파일을 선택하고

컨택스트 메뉴에서 Run As > Node Application을 실행하는 대신에,

Debug As > Node Application을 실행하면,

Node.js를 손 쉽게 디버깅하면서 개발 할 수 있다.

console.log로 변수의 값을 출력하면서 디버깅 할 수도 있지만

Java로 개발하듯이,

Eclipse에서 Breakpoint를 설정하고 한 행씩 실행하면서 디버깅 할 수 있다.


이상의 그림은 디버그 모드로 실행했을 때 제공되는 Debug Persperctice 화면으로,

현재 행까지 실행하고,

각 변수의 값을 우측 상단에 있는 Variables 탭에서 확인할 수 있다.

그림에서는

rows 배열 변수의 값과 각 원소의 Json 값이 출력되는 것을 볼 수 있다.


'Node.js > 기타' 카테고리의 다른 글

우리동네, 아이랑 놀곳 Ver.서울  (0) 2019.05.29
nineBatis (9batis)  (2) 2017.08.16
2. MariaDB 기반 CRUD 게시판 만들기  (23) 2017.08.05
3. 게시판 예제 보강  (4) 2017.08.05
4. GitHub에서 Node.js 소스 가져오기  (0) 2017.08.05

+ Recent posts