Node.js

[Node.js] 5. 웹 애플리케이션 만들기

반응형

이제 지금까지 배운 것을 통해서 Express 프레임워크를 통해 간단한 웹 애플리케이션을 만들어보자.


기존의 app.js에 추가하면 가독성이 떨어지므로, 프로젝트에 새로 app_file.js 파일을 만들어 진행하자.


이번 애플리케이션 실습을 통해 파일 시스템 모듈도 활용할 것이다.



데이터 값을 저장할 data 폴더, html로 나타낼 pug 파일을 저장할 views_file 폴더를 만들자.


이제 app_file.js를 작성해보도록 하자


1
2
3
4
5
6
7
var express = require('express');
var fs = require('fs'); // 파일 시스템 모듈 생성
var app = express(); // 애플리케이션 객체 생성
 
app.listen(3000, function(){
    console.log('Connected, 3000 port!');
})
cs


기존의 express를 require하고 app 변수로 express 객체를 생성하는 것에 추가로 fs 모듈을 생성한다.


listen을 통해 포트 번호 3000으로 접속시킨 기본 틀을 잡았다.


set을 통해 views에 있는 pug 파일을 사용할 수 있도록 선언하자.


1
2
app.set('views''./views_file');
app.set('view engine''pug');
cs

제목과 내용을 입력할 pug 파일을 만들어야 한다. 지난 실습에 사용했던 것과 구조가 경로만 빼면 모두 같다.

1
2
3
4
5
6
7
8
9
10
11
12
doctype html
html
  head
    meta(charset='utf-8')
  body
    form(action='/topic' method='post')
      p
        input(type='text' name='title' placeholder='title')
      p
        textarea(name='description')
      p
        input(type='submit')
cs

이를 나타낼 경로는 /topic/new라고 하자. get을 통해 경로를 설정해준다.
new.pug 파일을 렌더링을 이용해 가져온다.

1
2
3
app.get('/topic/new', function(req, res){
    res.render('new');
});
cs
 


/topic이라는 path에 post를 이용해서 제목과 내용을 전송하는 코드를 작성해보자.


1
2
3
4
5
6
7
8
9
10
11
app.post('/topic'function(req, res){
    var title = req.body.title;
    var description = req.body.description;
    fs.writeFile('data/'+title, description, function(err){
        if(err){
            console.log(err);
            res.status(500).send('Internal Server Error');
        }
        res.send('Success!');
    })
})
cs

body를 이용하려면 body-parser가 필요하단 것을 지난 시간에 배웠다.
따라서 app_file.js에 아래 두 줄을 추가해야 한다는 걸 다시 기억해보고 작성하자.

1
2
3
var bodyParser = require('body-parser');
 
app.use(bodyParser.urlencoded({ extended: false }))
cs


콜백함수의 내부 구조를 보면, fs.writeFile를 사용하고 있다. 이 기능을 통해서 프로젝트에 있는 /data 폴더에 해당 제목(title)이름으로 데이터가 생성되는 것이다. 만약 에러가 발생하면, 에러를 보여주는 코드도 추가하자.(경로 설정이 잘못 되면 위와 같은 에러가 출력될 것이다.)

이제 우리가 /topic/new에서 제목과 내용을 입력 후 전송하면, 프로젝트 data 폴더에 내 입력 정보가 들어오는 것을 볼 수 있다.
이렇게 사용자가 입력한 정보를 데이터로 저장할 수 있는 것이다.

이제 이 입력한 데이터들을 모두 출력해보고 싶어진다. 출력은? 바로 get을 사용한다. 우린 지금 post를 통해 데이터를 전송시키고 내부 data 폴더에 저장하는 것을 한 것이다.


출력된 데이터를 보여주는 페이지를 다시 만들어야 한다. view.pug를 views_file에 생성하고 app_file.js에서 get을 작성해보자

1
2
3
4
5
6
7
8
9
app.get('/topic', function(req,res){
    fs.readdir('data', function(err, files){
        if(err){
            console.log(err);
            res.status(500).send('Internal Server Error');
        }
        res.render('view', {topics:files});
    })
});
cs


이번에는 fs.readdir라는 기능을 사용했다. 이를 통해 첫 인수인 'data'에 있는 파일들을 읽을 수 있다. 콜백함수가 가지는 인수는 err와 files로 에러를 표출해주는 것과, 해당 data 파일 내용을 가져오는데 사용하는 files를 이용하는 것이다.

이제 렌더링을 통해 view.pug로 data 파일을 topics라는 변수이름을 통해 활용할 수 있다. (topics:files)

 
view.pug는 아래와 같이 작성하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
doctype html
html
  head
    meta(charset='utf-8')
  body
    h1 
      a(href='/topic') Server Side JavaScript
    ul
      each topic in topics
        li
          a(href='/topic/'+topic)= topic
    article
      h2= title
      h4= description
cs

기존의 작성은 같고, 이제 데이터를 출력하는 곳의 방식이 달라진다.
만약 우리가 가진 데이터가 많다면, 반복문을 통해서 출력해야 할 것이다.


each를 통해 반복문을 작성할 수 있는데, each topic in topics가 어떤 뜻일까?

기존의 app_file.js에서 topics로 data폴더의 files를 가져왔다. 
each라는 반복 기능을 통해 topics 안의 데이터들을 topic이라는 이름으로 설정하는 것이다.


li= topic이라고 작성하면, 내가 가지고 있는 데이터의 제목들이 모두 출력되는 것을 볼 수 있다.
이 데이터마다 하이퍼링크를 달고, 해당 데이터마다 path를 설정해주려면 위의 a태그 처럼 작성해주면 된다.



이제 하이퍼링크로 접속했을 때, 해당 타이틀의 내용을 출력하도록 만들고 싶다.
그리고 새로운 페이지가 아닌, 해당 페이지의 아래에 바로 출력 결과가 나오도록 할 것이다.

우선 app_file.js에서 /topic/:id로 해당 id마다 경로를 나오도록 만들며 아래와 같이 작성하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
app.get('/topic/:id', function(req, res){
    var id = req.params.id;
 
    fs.readdir('data', function(err, files){
        if(err){
            console.log(err);
            res.status(500).send('Internal Server Error');
        }
        
        fs.readFile('data/'+id, 'utf8', function(err, data){
            if(err){
                console.log(err);
                res.status(500).send('Internal Server Error');
            }
            res.render('view', {topics:files, title:id, description:data});
        })
    })
 
})
cs

params를 통해 id를 가져와 변수에 저장하고, 파일 시스템의 readdir 기능을 통해 데이터를 읽어오자. 오류를 나타내는 기능을 설정한 뒤, 파일 시스템의 readFile로 해당 id 경로마다 저장된 데이터를 읽어서 files, id, data를 렌더링한다.

view.pug를 다음과 같이 수정하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
doctype html
html
  head
    meta(charset='utf-8')
  body
    h1 
      a(href='/topic') Server Side JavaScript
    ul
      each topic in topics
        li
          a(href='/topic/'+topic)= topic
    article
      h2= title
      h4= description
cs

기존의 topics를 포함해서 article 속에 title과 description으로 제목과 내용을 불러오고 있다.

이제 출력된 데이터의 하이퍼링크를 누를 때마다 제목과 내용이 아래에 출력되는 것을 확인할 수 있다.


우리는 지금 새로운 정보를 입력하는 new.pug는 따로 /topic/new 경로를 통해 이루어지고 있다. 이것도 여기에 같이 합쳐서 이용하면 페이지를 넘나들지 않고 이용할 수 있을 것이다.

따라서 기존의 /topic/new 경로에 해당하는 get에 파일 시스템의 readdir로 data를 불러온다. 그 이후 렌더링에서 new.pug를 불러와 files를 topics으로 저장한다. 이제 이 topics로 view.pug처럼 반복문으로 사용이 가능하다.

1
2
3
4
5
6
7
8
9
app.get('/topic/new'function(req, res){
    fs.readdir('data'function(err, files){
        if(err){
            console.log(err);
            res.status(500).send('Internal Server Error');
        }
        res.render('new', {topics:files});
    })
})
cs


new.pug도 view.pug와 같은 구조로 수정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
doctype html
html
  head
    meta(charset='utf-8')
  body
    h1 
      a(href='/topic') Server Side JavaScript
    ul
      each topic in topics
        li
          a(href='/topic/'+topic)= topic
    article
      form(action='/topic' method='post')
        p
          input(type='text' name='title' placeholder='title')
        p
          textarea(name='description')
        p
          input(type='submit')
cs

이를 활용하려면 view.pug의 마지막에 아래 div로 new를 불러오는 a태그만 하나 생성하자
1
2
div
 a(href='/topic/new') new
cs


이제 한 페이지 안에서 새로운 데이터를 만들고 내용까지 볼 수 있는 파일이 만들어졌다.




+ 코드 개선

지금 우리 app_file.js에는 중복된 코드가 많다. 중복된 코드를 줄이고 시각적으로 보기 좋게 리팩토링해보자.


기존의 /topic과 /topic/:id는 path만 추가될 뿐 내부적으로 포함된 코드의 중복이 상당히 많다.

get에서 경로를 설정할 때 배열을 이용하면 두가지를 한꺼번에 설정할 수 있다.


1
2
3
app.get(['/topic', '/topic/:id'], function(req,res){
    ...
})
cs

파일 시스템으로 readdir를 통해 data를 불러오는 것은 둘다 똑같기 때문에 하나로 줄일 수 있다.

이제 id로 경로에 들어가는 /topic/:id는 if-else문을 통해 id가 있고 없고를 나누어 아래와 같이 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
app.get(['/topic''/topic/:id'], function(req,res){
    fs.readdir('data'function(err, files){
        if(err){
            console.log(err);
            res.status(500).send('Internal Server Error');
        }
        var id = req.params.id;
        if(id){
        //id값이 있을 때
            fs.readFile('data/'+id, 'utf8'function(err, data){
                if(err){
                    console.log(err);
                    res.status(500).send('Internal Server Error');
                }
                res.render('view', {topics:files, title:id, description:data});
            })
        }
        else{
        //id값이 없을 때
        res.render('view', {topics:files, title:'Welcome', description:'Hello, JavaScript for server.'});
        }
    })
})
cs

이렇게 작성하면 기존의 app.get('/topic/:id', ...) 코드는 지워도 문제가 없다.

이런식으로 같은 경로에서 뻗어나가는 곳은 배열을 통해 하나로 합쳐 정리를 할 수 있다.


반응형