지금까지는 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











+ Recent posts