세리프 따라잡기

nodejs-express 설치, helloworld 출력 및 코드 의미, 홈·상세 페이지 구현, 본문

Node.js

nodejs-express 설치, helloworld 출력 및 코드 의미, 홈·상세 페이지 구현,

맑은 고딕 2021. 1. 12. 20:39

시작하기 앞서 모든 코드들은 nodejs 부터 계속 이어지고(수정 및 추가) 있음을 알립니다!!

 

반복적으로 등장하는 일들, url parameter를 통해서 전달된 데이터를 받아서 뭔가를 처리하는 일이나 정적인 JS파일이나 이미지 파일 같은 것들을 컴퓨터로 읽어서 사용자에게 제공하거나, 로그인이나 보안적 이슈들을 해결하는 것들 = 이런 것들을 보다 안전하게 처리할 수 있도록 도와주는 게 nodejs 위에서 동작하는 Framework이다. 그중에 가장 보편적으로 사용되는 framework 인 Express를 배워보자!

 

1. 설치

expressjs.com/en/starter/installing.html 를 참고해 콘솔창에 npm install express --save를 입력시켜 설치하자. 설치가 완료되면 node_modules폴더에 express라는 하위 폴더가 생성되었음을 확인할 수 있다.

 

2. hello world 출력

expressjs.com/en/starter/hello-world.html 를 참고해 코드를 복사해 붙여 넣는다.

//const 변수는 constant, 즉 바뀌지 않는다는 뜻 = 왜 사용했냐? express라는 단어가 바뀔 일이 없기 때문에 고정을 시키는 것. 근데 공부할 땐 var를 이용해 할 것임으로 바꿔줌.
var express = require('express') //모듈을 로드해오는 코드
var app = express() //express를 함수처럼 호출하는데, 이 말은 express는 함수라는 뜻. 리턴값은 app이라는 변수에 담겼는데, 애플리케이션이라는 객체가 담기도록 한 것.
var port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
}) //정상 출력이 된다면 성공적으로 된 것이다.

expressjs.com/ko/4x/api.html api참조를 통해 여러 가지 중요한 기능들을 사용하는 방법들에 대한 매뉴얼이 있다. 이걸 통해 application은 이렇게 얻을 수 있고, 이 객체가 가지고 있는 함수들 중 get(route, routing: 갈림길에서 적당한 곳으로 방향을 잡을 수 있게 도와줌)을 볼 수 있다. 이런 것들을 통해 위 코드가 어떻게 짜였는지 알 수 있다.

 

+ 위의 get 코드는 최신 코드인데, 이를 앞서 배운 코드로 바꿔보자면 이러하다 (결국 동일한 코드다)

app.get('/', function(request, response){
  return response.send('hello world!')
});

app.get의 method를 호출하고 그 메소드의 첫 번째 인자로 path를 전달하는 걸 통해 라우팅을 하고 있는 것. 우리가 배운 이전 코드에서는 라우팅을 뭘 통해 했냐면 if문(pathname~)을 통해 했다. 기존 것도 잘 구현했으나, 이번에 배운 방식은 이전 방식에 비해 가독성 부분에서 훨씬 더 좋다. (if문 중첩이 많아질수록 복잡해지기 때문)

 

+ 위의 listen 코드는 최신 코드, 배운 코드로 변형

app.listen(port, function () {
  console.log(`Example app listening at http://localhost:${port}`)
});

listen 메소드에 1번째 인자로 3000을 주면 저 listen이라는 메소드가 실행될 때 웹 서버가 실행되면서 3000번 포트에 listening 하게 되고, 성공하면 뒤에 있는 포트를 실행하게 해 준다. 이는 이전에 배운 코드 app.listen(3000); 부분과 동일한 것을 알 수 있다.

 

~ 위 처음 부분이 제일 중요한 부분!! 이게 골격이 되는 부분이기 때문 ~

 

3. 홈페이지 구현

var express = require('express')
var app = express()
var port = 3000
var fs = require('fs'); //복붙
var template = require('./lib/template.js'); //복붙

app.get('/', function (request, response) {
  fs.readdir('./data', function (err, filelist) { //이전에 만든 코드를 복붙, 이를 실행 시키기 위해 위에 변수 fs와 template 또한 복붙한다.
    var title = 'Welcome';
    var description = 'Hello, Node.js';
    var list = template.list(filelist);
    var html = template.html(title, list,
      `<h2>${title}</h2>${description}`,
      `<a href="/create">create</a>`);
    response.send(html); //이전의 writeHead(200); response.end(html) 부분은 send 로 퉁칠 수 있다.
  })
}); //성공적으로 잘 동작하는 것을 볼 수 있다.

app.get('/page', function (request, response) {
  return response.send('/page')
});

app.listen(port, function () {
  console.log(`Example app listening at http://localhost:${port}`)
});

이전 nodejs만 사용하던 코드 같은 경우는 실제로 정의되는 부분부터 사용되는 부분까지의 거리가 너무 멀었다면, express가 제공하는 라우팅 방식을 사용하게 되면 각각의 처리하는 부분을 req, res를 각각 구현하기 때문에 서로 필요한 것들끼리 잘 모인 느낌을 받을 수 있다.

 

4. 상세 페이지 구현

이전 nodejs만 사용하던 코드에선 쿼리스트링을 통해 가져오고 싶은 데이터를 웹 app에게 전달했다. 이때 주소가 http://localhost:3000/?id=블라블라였는데, 요즘 트렌드에서는 semantic url이라고 주소에 쿼리스트링이 들어가는 것을 선호하지 않는다(검색엔진 최적화와 관련이 있다). 이전처럼 페이지를 구현하면서 동시에 전보다 깔끔하게 해보자!

 

//경로를 /page로 지정, 콜백 함수가 실행될 때 HTML이라는 값을 가져오게끔 해보자
app.get('/page/:pageid', function (request, response) { // '경로/:pageid'를 붙여 입력값을 마크한다.
  response.send(request.params) //return은 생략이 가능해서 제거, request.params 안에 HTML값이 있는 것
}); //{"pageid":"HTML"} 라고 페이지에 출력되면 성공적!

// url 모습= http://localhost:3000/page/HTML

URL path를 통해 parameter를 전달하는 경우에 어떻게 express에 섞어서 처리하는지에 대한 것을 살펴본 것. = 라우팅 기법

 

  • 구현
var path = require('path'); //경로 복붙
var sanitizehtml = require('sanitize-html'); //sanitize 복붙

app.get('/page/:pageid', function (request, response) {
  fs.readdir('./data', function (err, filelist) { //경로를 읽기 위해 위에 경로 변수 복붙해오기
    var fillterdid = path.parse(request.params.pageid).base; //쿼리데이터.id라 적힌 이하 모든 부분을 우리가 쓸 request.params.pageid라고 수정
    fs.readFile(`data/${fillterdid}`, 'utf8', function (err, description) {
      var title = request.params.pageid; //수정
      var sanitizedtitle = sanitizehtml(title); //sanitize 쓰기 위해서 위에 변수 복붙해오기
      var sanitizeddescription = sanitizehtml(description, {
        allowedTags:['h1']
      });
      var list = template.list(filelist); // template.js 파일에서 list값 함수를 수정해준다.
      var html = template.html(sanitizedtitle, list,
        `<h2>${sanitizedtitle}</h2>${sanitizeddescription}`,
        `<a href="/create">create</a>
        <a href="/update?id=${sanitizedtitle}">update</a> 
        <form action="delete_process" method="post">
        <input type="hidden" name="id" value="${sanitizedtitle}">
        <input type="submit" value="delete">
        </form>`);
      response.send(html);
    });
  });
});

+ template.js 파일 수정은 아래와 같이

    list: function (filelist) {
      var list = '<ul>';
      var i = 0;
  
      while (i < filelist.length) {
        list = list + `<li><a href="/page/${filelist[i]}">${filelist[i]}</a></li>`; //여기에 /page/라고만 수정해주면 된다.
        i = i + 1;
      }
      list = list + '</ul>';
      return list;
    }

 

5. 페이지 구현

  • 생성(create) 버튼
var qs = require('querystring'); //복붙 추가

app.get('/create', function (request, response) { //생성 버튼 구현
  fs.readdir('./data', function (err, filelist) { //이전의 코드 복붙
    var title = 'WEB - create';
    var list = template.list(filelist);
    var html = template.html(title, list,
      `<form action="create_process" method="post">
    <p><input type="text" name="title" placeholder="title"></p>
    <p>
        <textarea name="description" placeholder="description"></textarea>
    </p>
    <p>
        <input type="submit">
    </p>
</form>`, ''); //post방식으로 받는 중
    response.send(html);
  });
})

app.post('/create_process', function (request, response) { //post 방식으로 받은 곳 코드
  var body = ''; //이전 코드 복붙
  request.on('data', function (data) {
    body += data;
  });
  request.on('end', function () {
    var post = qs.parse(body); //qs를 쓰기 위해 위에 변수 복붙
    var title = post.title;
    var description = post.description;
    fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
      response.writeHead(302, { location: `/?id=${title}` });
      response.end();
    });
  });
}) //성공적으로 실행되어 생성이 되는 것을 볼 수 있다.
  • 수정(update) 버튼
app.get('/page/:pageid', function (request, response) {
  fs.readdir('./data', function (err, filelist) {
    var fillterdid = path.parse(request.params.pageid).base;
    fs.readFile(`data/${fillterdid}`, 'utf8', function (err, description) {
      var title = request.params.pageid;
      var sanitizedtitle = sanitizehtml(title);
      var sanitizeddescription = sanitizehtml(description, {
        allowedTags: ['h1']
      });
      var list = template.list(filelist);
      var html = template.html(sanitizedtitle, list,
        `<h2>${sanitizedtitle}</h2>${sanitizeddescription}`,
        `<a href="/create">create</a>
        <a href="/update/${sanitizedtitle}">update</a> 
        <form action="delete_process" method="post">
        <input type="hidden" name="id" value="${sanitizedtitle}">
        <input type="submit" value="delete">
        </form>`); // '/update?id=' 부분을 '/update/'로 수정
      response.send(html);
    });
  });
});

// ~code~

app.get('/update/:pageid', function (request, response) { //수정 버튼 구현 + pageid를 넣어줌
  fs.readdir('./data', function (err, filelist) { //이전 코드 복붙
    var fillterdid = path.parse(request.params.pageid).base; //쿼리데이터.id 이하 수정
    fs.readFile(`data/${fillterdid}`, 'utf8', function (err, description) {
      var title = request.params.pageid; //수정
      var list = template.list(filelist);
      var html = template.html(title, list,
        `
        <form action="/update_process" method="post">
        <input type="hidden" name="id" value="${title}">
    <p><input type="text" name="title" placeholder="title" value="${title}"></p>
    <p>
        <textarea name="description" placeholder="description">${description}</textarea>
    </p>
    <p>
        <input type="submit">
    </p>
</form>`, `<a href="/create">create</a> <a href="/update/${title}">update</a>`);
      response.send(html); // 'update_process' 부분 앞에 '/update_process'로 수정
    });
  });
})

app.post('/update_process', function (request, response) { //이후 코드들은 이전의 코드 복붙
  var body = '';
  request.on('data', function (data) {
    body += data;
  });
  request.on('end', function () {
    var post = qs.parse(body);
    var id = post.id;
    var title = post.title;
    var description = post.description;
    fs.rename(`data/${id}`, `data/${title}`, function (err) {
      fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
        response.writeHead(302, { location: `/page/${title}` });
        response.end();
      });
    })
  });
})

// + 이전의 쿼리스트링으로 표시된 '/?id=' 부분들 전부 clean url로 수정
  • 삭제(delete) 버튼
app.get('/page/:pageid', function (request, response) {
  fs.readdir('./data', function (err, filelist) {
    var fillterdid = path.parse(request.params.pageid).base;
    fs.readFile(`data/${fillterdid}`, 'utf8', function (err, description) {
      var title = request.params.pageid;
      var sanitizedtitle = sanitizehtml(title);
      var sanitizeddescription = sanitizehtml(description, {
        allowedTags: ['h1']
      });
      var list = template.list(filelist);
      var html = template.html(sanitizedtitle, list,
        `<h2>${sanitizedtitle}</h2>${sanitizeddescription}`,
        `<a href="/create">create</a>
        <a href="/update/${sanitizedtitle}">update</a> 
        <form action="/delete_process" method="post">
        <input type="hidden" name="id" value="${sanitizedtitle}">
        <input type="submit" value="delete">
        </form>`); //"delete_process" 를 "/delete_process"로 변경
      response.send(html);
    });
  });
});

// ~ code ~

app.post('/create_process', function (request, response) {
  var body = '';
  request.on('data', function (data) {
    body += data;
  });
  request.on('end', function () {
    var post = qs.parse(body);
    var title = post.title;
    var description = post.description;
    fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
      response.redirect(`/page/${title}`); //redirect 부분 수정
    });
  });
})

// ~ code ~

app.post('/update_process', function (request, response) {
  var body = '';
  request.on('data', function (data) {
    body += data;
  });
  request.on('end', function () {
    var post = qs.parse(body);
    var id = post.id;
    var title = post.title;
    var description = post.description;
    fs.rename(`data/${id}`, `data/${title}`, function (err) {
      fs.writeFile(`data/${title}`, description, 'utf8', function (err) {
        response.redirect(`/page/${title}`); //간편하게 수정
      });
    })
  });
})

app.post('/delete_process', function (request, response) { //삭제 구현, 아래는 이전 코드 복붙
  var body = '';
  request.on('data', function (data) {
    body += data;
  });
  request.on('end', function () {
    var post = qs.parse(body);
    var id = post.id;
    var fillterdid = path.parse(id).base;
    fs.unlink(`data/${fillterdid}`, function (err) {
      response.redirect('/'); //writeHead(302, { location: `/` }); response.end(); 이런식이었던 코드를 express에서 지원하는 더 쉽고 빠른 코드로 수정.
    })
  });
}) //잘 삭제되는 것을 볼 수 있다. + redirect 또한 정상적으로 실행되는 것을 볼 수 있다.

app.listen(port, function () {
  console.log(`Example app listening at http://localhost:${port}`)
});

stackoverflow.com/questions/19035373/how-do-i-redirect-in-expressjs-while-passing-some-context 참고하여 redirect 부분을 수정.

 

nodejs에서 만들었던 예제를 express 버전으로 전환하는 작업이 끝났는데, 보면 라우트 기능이 대부분이다. 실제로 웹 freamwork를 배우든 간에 제일 먼저 체크해야 할 것은 설치하는 방법과 어떻게 라우트 하는가를 알아내는 것이다. path 별로 어떻게 응답하는지, get과 post 방식으로 접속했을 때 어떻게 구분해서 응답하는지를..

이런 부분들을 살펴보는 것이 웹 프레임워크를 다룰 때 첫 번째이고 이 작업을 할 수 있게 된다면 이미 반절은 해낸 것이다! 😁

Comments