ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Node.js & MySQL] 코드 리팩토링, 이슈 정리
    생활코딩/WEBn 2021. 1. 24. 11:47

    다음 단계인 Express로 가기전에 나름대로 코드를 정리하는 시간을 가졌다.

    생활코딩에서는 다음 단계로 넘어갈 때 원초적인 골격 코드를 가지고 해당 주제에만 초점을 맞추는데

    아까워서 전 단계에서 실습했던 코드를 그대로 가지고 추가추가 하는 과정을 거쳤더니

    WEB3까지 마친 지금은 코드가 조각조각 이어붙인 넝마주이가 되고 말았다.

     

    현재 이정표는 아래와 같다.

    WEB1 HTML&Internet -> WEB2 CSS -> WEB2 JavaScript -> WEB2 Node.js -> WEB3 Node.js & MySQL

     

    WEB1 HTML&Internet: 기본적인 HTML 페이지 골격 작성

    WEB2 CSS: 디자인 부분을 스타일시트로 이동 & <link rel="stylesheet" type="text/css" href="">로 연결

    WEB2 JavaScript: Night <-> Day 버튼으로 야간모드 변경하는 스크립트 작성해서 <script src="">로 연결

    WEB2 Node.js: 글 생성/편집/삭제 기능 추가, HTML 작성용 template.js 파일 추가/임포트

    WEB3 Node.js & MySQL: 위의 글 생성/편집/삭제 기능을 MySQL에 연동, 글에 연결되는 저자 생성/수정/삭제 기능 구현

     

     

    현재 상태는 이렇다. 구조는 그대로 유지하고 추가 분리/코드 리팩토링만 진행했다!

    Project

    ├─ main.js
    ├─┬ data

    │  └─┬ lib

    │         └─┬ author.js

    │                ├ db.js

    │                ├ template.js

    │               └ topic.js

    ├─┬ public

    │  └─┬ dayNightButton.js

    │         └ style.css

    └─ package.json

     

     

    기존 main.js에서는 라우팅을 if문으로 처리했다.

    그리고 topic, author 스크립트를 임포트해서, 알맞은 메소드를 찾아가게 하는 방식이다.

    var http = require('http');
    var url = require('url');
    var path = require('path');
    var topic = require('./data/lib/topic');
    var author = require('./data/lib/author');
    
    
    var app = http.createServer(function(request, response){
        var _url = request.url;
        var id = url.parse(_url, true).query.id;
        if (id !== undefined){
          id = path.parse(id).base;
        };
        var pathname = url.parse(_url, true).pathname;
        var ext = '';
        if(pathname.indexOf('.') > -1){
          ext = pathname.split('.').splice(-1)[0];
        };
        if(ext){    // 확장자가 있으면: html이 아니면
            if(0 <= ['css', 'js'].indexOf(ext)){
                topic.response_fmt.sendContent(response, ext, pathname);
            } else {  // 확인불가
                topic.response_fmt.notFound(response);
            }
        } else {    // html이면
            if (pathname === "/"){  // 홈 요청이면
    		    topic.page(id, request, response);  // id 조회 가능하면 해당 페이지, 아니면 404
            } else if (pathname === "/create"){
    			topic.create(id, request, response);
    		} else if (pathname === "/update"){  // 편집페이지면
    			topic.update(id, request, response);
            } else if (pathname === "/process_create"){ // 처리과정이면
    			topic.process_create(id, request, response);
    		} else if (pathname === "/process_update"){
    			topic.process_update(id, request, response);
    		} else if (pathname === "/process_delete") {
    			topic.process_delete(id, request, response);
    		} else if (pathname === "/process_undo") {
    			topic.process_undo(id, request, response);
    		} else if (pathname === "/author") {
    			author.home(request, response);
    		} else if (pathname === "/author/process_create") {
    			author.create_process(request, response);
    		} else if (pathname === "/author/process_update") {
    			author.update_process(request, response);
    		} else if (pathname === "/author/update") {
    			author.update(id, request, response);
    		} else if (pathname === "/author/process_delete") {
    			author.delete_process(request, response);
    		} else if (pathname === "/author/process_undo") {
    			author.process_undo(request, response);
    		} else {
                response.writeHead(404);
                response.end('Page Not Founded');
            }
          }
    });
    app.listen(3000);
    

    메소드를 호출할 때 객체가 주제별로(topic, author) 정리되어서 알아보기는 쉽지만

    어차피 경로를 같은 이름의 메소드로 처리할 거면 메소드명을 호출해도 되지 않을까 싶었다.

     

    그래서 base라는 변수에 author와 topic 모듈을 담고,

    입력이 들어온 경로를 파싱해서 base[dir][property]로 처리하도록 했다.

    다만 /author의 홈페이지는 /author/home이 아니라 /author다보니 계속 topic쪽으로 넘어가서,

    if(basePath === "/author") 처리를 따로 해주었다.

    var http = require('http');
    var url = require('url');
    var path = require('path');
    var fs = require('fs');
    
    var base = {
    	'/': require('./data/lib/topic'),
    	'/author': require('./data/lib/author')
    };
    
    var app = http.createServer(function(request, response){
        var _url = request.url;
        var pathname = url.parse(_url, true).pathname;
    	var dir = path.parse(pathname).dir;
    	var basePath = "/" + path.parse(pathname).base;
    	
    	if (basePath === "/author"){            // /author 페이지 처리
    		base['/author']['/'](request, response);
        } else if (base.hasOwnProperty(dir)) {  // /이나 /author/editPage 요청이면 
    		if (base[dir].hasOwnProperty(basePath)){
    			base[dir][basePath](request, response);
    		} else {
    			response.writeHead(404);
    			response.end('Page Not Founded');
    		}		
    	} else if (dir === '/public' && fs.existsSync(__dirname + pathname)) {
    		var ext = path.parse(pathname).ext.replace(".", "");
    			response.writeHead(200, {'Content-Type': `text/${ext}`});
    			response.write(fs.readFileSync(__dirname + pathname, 'utf8'));
    			response.end();
    	} else {
    		response.writeHead(404);
    		response.end('Page Not Founded');
    	}
    
    });
    app.listen(3000);
    

    마지막 else if문은 css, js 파일을 연결하기 위한 용도로 작성했다.

     

    마찬가지로 author, topic에 속하는 메소드들도 입력이 들어오는 경로명으로 새 이름을 지어줬다.

    어쨌든 작업별로 메소드를 만들어줘야 한다는 점은 똑같아서 크게 개선되었는지는 모르겠다 ...

    그래도 메소드를 추가하더라도 main.js에 if문을 추가해줄 필요는 없게 되었다!

    module.exports = {
    	'/': function(request, response){
            // 
    	},
    	'/create': function(request, response){
            //
        },
     	'/update': function(request, response){
            // 
    	},
    	'/process_create': function(request, response){
            //
        },
    	'/process_update': function(request, response){
            //
        },
        '/process_delete': function(request, response){
            //
    }

     

     

    template.js의 메뉴부분을 처리하면서 고민을 많이 했다.

    topic과 author 페이지는 메뉴바/헤딩은 똑같고 오른쪽에 들어가는 컨텐츠만 바뀐다.

    그러면 탬플릿을 메뉴바 + 컨텐츠로 나누어서 구성하고, 메뉴바는 재사용을 하면 되겠다고 생각했는데,

    약간의 문제가 있었다.

     

    topic의 경우, 특정 페이지 조회로 가면 목록에서 현재 페이지를 다른 색으로 표시해야 한다(id="active").

    또 위의 active가 활성화되는 경우 자체적으로 만들었던 이전 페이지/다음 페이지 버튼도 출력해야하고,

    Update/Delete가 표시되어야 했다.

     

    기본 틀은 비슷하지만 조건에 따라 달라지니까 옵션 변수를 만들어서 조건을 전달하기로 했다.

    authorPage가 true면 author, false면 topic을 처리하고,

    create, update 변수가 true면 해당 페이지 상세 내용을 표시한다.

    추가로, 혹시라도 없는 id 조회나 home값인 0을 요청하면 리다이렉트를 하기로 했다.

     

    탬플릿 함수에서 옵션을 받기로 하고 아래와 같이 수정하고,

    /*
    var option = {
        authorPage: false,
        create: false,
        update: false
    }
    */
    
    module.exports = {
      Edit: function(option, id, callback){   // 홈은 아이디 0
        var active = ` id="active"`;
        var edit = ``;  // 조건에 따라 편집메뉴 저장할 변수
        var home = ``;  // Home active 지정여부
        var author = ``  // 마찬가지
        var create = '';
        var update = '';
    
        if (option.authorPage) {   // authoPage면
          author = active;
        } else if ( id === 0 && !option.create ){  // 홈이고 create 페이지가 아니면
      	  home = active;
        } else if ( option.create ) {
       	 create = active;
        } else {         // 그외의 경우, Update는 편집메뉴 필요해서 아래에서 if문으로 처리
      	  if (option.update){
        	update = active;
          };
          edit += `
          <li><a href="/update?id=${id}" class="MenuEdit" ${update}>Update</a></li>
          <li><form action="process_delete" method="post" onsubmit="return confirm('do you want to delete this file?')">
          <input type="hidden" name="id" value="${id}">
          <input type="submit" value="Delete" class="MenuEdit">
          </form></li>`;
        };
        menu = `
        <ul>
        <li><a href="/" ${home}class="MenuEdit">Home</a></li>
        <li><a href="/create" class="MenuEdit" ${create}>Create</a></li>
        ${edit}
        <li><a href="/author" ${author}class="MenuEdit">AuthorPage</li>
        </ul>`;
        callback(menu);
    }

     

    혹시라도 없는 id 조회나 home값인 0을 요청은 위의 Edit을 불러오는 author나 topic단에서 처리하기로 했다.

    author의 경우, 실제 update를 처리하기 전에 값이 있는지 확인하는 절차를 거쳤다. 여기는 홈이 없으므로 인덱스가 0인 데이터가 없고,

    '/update': function(request, response){
      var id = url.parse(request.url, true).query.id;
      db.query('SELECT * FROM author WHERE id = ?', id, function(err, result){
        if (err) { throw err; };
        if (result.length === 0){   // 조회했는데 값이 나오지 않는 경우
          response.writeHead(302, {
          'Location': '/author'
          });
          response.end();
          return 0;	
        } else {
          // id가 존재하는 경우
        }
      });
    }

    topic의 경우에는 if 문 안에 result[0].id === 0 을 추가했다.

    '/update': function(request, response){
    	var id = url.parse(request.url, true).query.id;
    	db.query('SELECT topic.*, author.name as author_name, author.profile as author_profile FROM topic LEFT JOIN author ON topic.author_id=author.id WHERE topic.id = ?', [id], function(err, result){
    		if (err) { throw err; };
    		if (result.length === 0 || result[0].id === 0){   // 값이 나오지 않으면, 없는 id이면
    			response.writeHead(302, {         // 홈으로 리다이렉트
    				'Location': `/`
    			});
    			response.end();
    			return 0;
    		} else {
    			// 값이 조회되는 경우
    		};
        });
    }

    이제 이 다음에서 전달된 데이터를 처리하면 완료!

     

     

    CSS도 정리해보기로 했다. 코드와 비슷하게 생각나는대로 추가했더니 가독성이 0에 수렴하는 상태가 되었다.

    혹시 해서 인터넷에 CSS guideline을 검색해보았더니, 구글의 HTML/CSS Style Guide가 있다!

    적당히 참고해서 아래와 같이 작업해보기로 했다.

     

    • 띄어쓰기
      • 들여쓰기는 띄어쓰기 2칸으로 하고 tab과 혼용하지 말고, 의미 없는 문장 끝 띄어쓰기는 지양한다.
      • 선택자와 중괄호 사이에 띄어쓰기 1칸(줄바꿈 X), 콜론 뒤에서 띄어쓰기 1칸을 넣는다.
    • 선택자/클래스
      • id나 클래스명은 기능적이고 일반적인 이름으로, 가능한 짧게 지어서 목적이 잘 드러날 수 있도록 한다.
      • 여러 선택자에 동일한 속성을 부여할 경우, 선택자 쉼표 뒤에 줄바꿈을 넣고, 마지막 선택자에서 중괄호를 연다.
      • 아이디나 클래스명 앞에 선택자를 쓰는 건 꼭 필요한 경우가 아니면 생략한다.
      • 규칙 사이에는 줄바꿈을 한다.
    • 가능하면 짧게
      • font-size: 100%; 보다는 font: 100%;로 쓴다.
      • 꼭 필요한 경우가 아니면 0 뒤에 단위는 쓰지 않고, -1 ~ 1 사이의 값에서 0.8은 .8처럼 쓴다.
    • 기타 기호 규칙
      • 가능한 홑따옴표('')를 쓰고, url() 값 안에서는 따옴표를 쓰지 않는다.
      • 속성 이름, 속성, 속성값, 선택자 등에는 소문자만 사용하고, 단어간 구분은 하이픈으로 한다
    • 특이하게 속성 선언은 알파벳순으로 한다!

     

    그리고 유효한 CSS를 사용하라는 조언을 하면서 w3에서 운영하는 CSS-validator 사이트를 소개해준다.

    url을 입력해서 검증할 수 있다. 테스트를 해보았더니 아래처럼 나온다.

    기초적인 css구문 뿐이라 걸린 것이 없었다. 이후 복잡한 코드를 작성하게 되더라도 꼭 테스트해보는 습관을 가져야겠다.

     

    이제 기존 CSS를 새로 작성해본다. -너무 길어서 접었다.

    더보기
    body {
      color: black; background: white;
      margin:0;
    }
    h1 {
      font-size:45px;
      text-align:center;
      padding:20px;
      border-bottom:2px solid gray;
      margin:0;
    }
    #maindiv {
      display:grid;
      grid-template-columns:1fr 3fr;
    }
    a {
      color:blue;
    }
    .left {
      border-right:gray solid 2px;
      min-width: 200px;
    }
    .right {
      padding-left:20px;
      padding-right:20px;
    }
    .menu {
      border-bottom:gray 2px solid;
    }
    #active {
      color:red;
    }
    #buttons {
      display:grid;
      grid-template-rows:1fr 1fr;
    }
    #buttons .buttons {
      display:grid;
      grid-template-columns: 1fr 1fr;
    }
    .button1 {
      text-align: left;
      padding:5px;
      margin-left:0;
    }
    .button2 {
      text-align: right;
      margin-right: 0;
      padding:5px;
    }
    .comlogo {
      text-align:center;
      padding:40px;
    }
    .logo {
      max-width: 200px;
      max-height: 100px;
    }
    .handler {
      background-color: black;
      color: white;
      margin:7px;
    }
    
    .button3 {
      text-align: center;
      background: none!important;
      padding: 0!important;
      cursor: pointer;
      font-size: inherit;
      color: blue;
      font-family:Times;
      display: block;
      margin: auto;
    }
    
    #postform {
      padding: 10px;
    }
    
    .MenuEdit {
      background: none!important;
      border: none;
      padding: 0!important;
      text-decoration: underline;
      cursor: pointer;
      font-size: inherit;
      color:inherit;
      font-family:Times;
    }
    
    .author {
      color: gray;
      text-align: right;
    }
    
    .authorTable {
      width: 80%;
      height: 50%;
      max-width: 600px;
      padding: 10px;
      margin: 20px;
      border-collapse: collapse;
    }
    
    textarea {
    	width: 90%;
    }
    
    @media(max-width:800px) {
      #maindiv {
        display:block;
      }
      h1, .left, .menu {
        border:none;
      }
      #left-top {
        display:grid;
        grid-template-columns: 2fr 1fr;
      }
      #left-top .list {
        margin:0;
      }
    
       .authorTable {
    	width: 80%;
    	word-break:break-all;
        padding: 10px;
        max-width: 600px;
      }
    	
    	textarea {
    		width: 80%;
    	}
    }

     

    선언 순서를 어떻게 해야할지 잘 모르겠는데 아이디 > 클래스 > 일반 순으로 재배열하고, 동일 등급(?) 내에서는 알파벳 순으로 구분했다.

    authorTable 같은건 author-table로 바꿔야하고, button1, button2를 좀 의미있는 이름으로 바꿔야겠다.

    아래는 정리를 마친 코드. 마치고보니 테이블을 좀 예쁘게 바꾸고 싶어서 효과를 넣어보았다.

    더보기
    #active {
      color: red;
    }
    
    #button-view {
      display: grid;
      grid-template-rows: 1fr 2fr;
    }
    
    #button-view .button-move {
      display: grid;
      grid-template-columns: 1fr 1fr;
    }
    
    #layout-main {
      display: grid;
      grid-template-columns: 1fr 3fr;
    }
    
    .author {
      color: gray;
      text-align: right;
    }
    
    .author-table {
      border: none;
      border-collapse: collapse;
      height: 50%;
      margin: 20px;
      max-width: 600px;
      padding: 10px;
      width: 80%;
    }
    
    .button-prev {
      margin-left: 0;
      padding: 5px;
      text-align: left;
    }
    
    .button-next {
      margin-right: 0;
      text-align: right;
      padding: 5px;
    }
    
    .button-edit {
      border-color: none;
      background-color: whitesmoke;
      color: gray;
      cursor: pointer;
      font-size: 90%;
      font-family: Times;
    }
    
    .form-post {
      padding: 10px;
    }
    
    .handler {
      background-color: black;
      color: white;
      margin: 7px;
    }
    
    .img-logo {
      padding: 40px;
      text-align: center;
    }
    
    .layout-left {
      border-right: gray solid 2px;
      min-width: 200px;
    }
    
    .layout-right {
      padding: 20px;
    }
    
    .logo {
      max-width: 200px;
      max-height: 100px;
    }
    
    .menu {
      border-bottom: gray 2px solid;
    }
    
    .menu-edit {
      background: none !important;
      border: none;
      color: #333333;
      cursor: pointer;
      font: inherit;
      font-family: Times;
      padding: 0 !important;
      text-decoration: underline;
    }
    
    .table-idx {
      background-color: gray;
      border-bottom: gray;
      color: white;
      padding: 1px;
      text-align: center;
    }
    
    a {
      color: blue;
    }
    
    body {
      color: black;
      background: white;
      margin: 0;
    }
    
    h1 {
      border-bottom: 2px solid gray;
      font: 45px;
      margin: 0;
      padding: 20px;
      text-align: center;
    }
    
    textarea {
      width: 90%;
    }
    
    td {
      border-bottom: 1px solid #ddd;
      padding: 3px 6px 3px 3px;
      border-left: none;
      border-right: none;
    }
    
    th {
      border-bottom: 1px solid #ddd;
      background-color: gray;
      color: white;
    }
    
    
    @media(max-width: 800px) {
      #layout-main {
    	display: block;
      }
    
      #layhout-left-top {
        display: grid;
        grid-template-columns: 2fr 1fr;
      }
    
      #layout-left-top,
      .list {
        margin: 0;
      }
    
      .author-table {
        width: 80%;
        word-break: break-all;
        padding: 10px;
        max-width: 600px;
      }
    
      .layout-left,
      .menu,
      h1 {
        border: none;
      }
    
      textarea {
        width: 80%;
      }
    }
    

    테이블:

     

    그리고 생활코딩에서 내줬던 과제인 검색, 페이지, 정렬기능을 구현해보았다.

    이쪽은 글이 길어져서 다른 글로 분리했다: [Node.js & MySQL] 도전과제: 검색/페이징/정렬

     


    오류를 만나면서 배운 것들은 아래에 정리해두었다.

     

     

    [URL.parse] url.parse(request.url).query.id의 값은 string

    db의 id는 number, url.query의 id는 string이라서 조건문을 쓰다가 혼란스러운 경험을 했다.

    if (id === 0) 을 했는데, id가 분명히 0인데 조건이 자꾸 false로 나오는 것.

    typeof를 해보고서야 알았다. 생각해보니 url은 string이고 string을 파싱했으니 당연히 string이 나올텐데,

    db의 id도 조건 체크를 하다보니 중간에 혼선이 있었나보다.

    url 파싱값은 string이므로 비교할 때는 값을 string으로 바꿔서 비교해야 한다.

     

     

    [form action의 URL] 절대/상대경로

    AuthorPage를 만들면서 생겼던 이슈다.

    처음에 AuthorPage는 Create 페이지를 따로 만들지 않았더니 Create post가 도착하는 경로가 Update와 달라서 혼란스러웠다.

    (author의 create는 홈/author/process_create로 가야하는데  홈/process_create으로 가서, 거기서 처리하는 topic db에 그런 컬럼이 없다는 오류를 내보냈다.)

     

    이유를 살펴보니 form URL에는 절대경로(An absolute URL: http://www로 시작하는 전체경로)와,

    상대경로(A relative URL: 웹사이트 안에서 도착할 경로)가 있고,

    상대경로를 쓰는 경우 form이 위치한 디렉토리에 따라 post의 도착 위치가 달라질 수 있기 때문이었다.

     

    내 경우 Create 명령이 실행되는 디렉토리는 (홈 - 파일위치: 홈/author)이고,

    Update 명령이 실행되는 디렉토리는 (홈/author- 파일위치: 홈/author/update)여서

    똑같이 process_action으로 보내더라도 Create는 홈/process_create로, Update는 홈/author/process_update로 보내졌던 것.

    경로를 절대경로로 바꿔주거나, Create 페이지를 새로 만들어서 form 디렉토리를 똑같이 맞춰주면 해결된다.

    나는 Create 페이지를 새로 생성하는 쪽을 택했다.

     

    Create: url/create 에서 실행

    // 실행경로가 home/create 인 경우
    var action = 'process_create';
    var form = `<form action="${action}" method="post" id="postform">
                  <input type="submit">
                </form>`
    // home/process_create 으로 보내진다

    Update: url/author/update 에서 실행

    // 실행경로가 home/author/update 인 경우
    var action = 'process_update';
    var form = `<form action="${action}" method="post" id="postform">
                  <input type="submit">
                </form>`
    // home/author/process_create 으로 보내진다

     

    [파일 연결] 다른 디렉토리에 있는 css, js script 연결하기

    html 파일 구성을 data/lib/template.js에서 처리하면서, public/stylesheet.css와 바로 연결할 수 없는 문제가 생겼다.

    Node.js에서 워킹 디렉토리를 확인하려면 __dirname 값을 보면 된다.

    // data/lib/template.js
    console.log(__dirname);

    data/lib/template.js에서 워킹디렉토리를 확인하고, 해당 디렉토리 기준으로 style.css 파일 경로를 바꿔주었다.

    워킹 디렉토리가 data/lib/이기 때문에 css파일에 접근하려면 아래와 같이 해야한다.

    [참조] stackoverflow - Link a .css on another folder

    <link rel="stylesheet" type="text/css" href="../../public/style.css">

    그리고 http를 처리하는 main.js에서도 public/style.css 요청을 처리할 수 있도록 조치를 해야한다.

    // main.js
    var http = require('http');
    var url = require('url');
    var path = require('path');
    var fs = require('fs');
    
    var app = http.createServer(function(request, response){
      var _url = request.url;
      var pathname = url.parse(_url, true).pathname;
      var dir = path.parse(pathname).dir;
      var basePath = "/" + path.parse(pathname).base;
    
      // if문을 추가해서, /public 경로로 존재하는 파일에 대한 요청이 들어오면 처리하도록 했다
      if (dir === '/public' && fs.existsSync(__dirname + pathname)) {
        var ext = path.parse(pathname).ext.replace(".", "");
        response.writeHead(200, {'Content-Type': `text/${ext}`});
        response.write(fs.readFileSync(__dirname + pathname, 'utf8'));
        response.end();
      }
      // 나머지 처리
    });

    이제 css, js 파일이 잘 불러와진다!

     

     

    [Node.js] mysql 모듈에서 읽어온 데이터 처리하기(callback)

    비동기 프로그래밍이 아직 낯설어서 헤맸던 부분이다.

    처음에는 아래처럼 db.query()로 읽어온 다음에 변수에 할당하고, 이후 처리를 하려고 했는데

    // 시도했던 구문
    var data = '';
    db.query('SELECT * FROM topic', function(err, result){
        data = result.slice();
    });
    console.log(data);  // '', 데이터가 추가되지 않았다

    db.query() 문 내부에서만 result가 복사된 상태로 유지되고, 메소드가 끝나는 순간 해당 값이 사라져버리고 말았다.

    스택오버플로우를 찾아보니 비슷한 사례(node js - Node mysql - Result is undefined)가 있어서 참고했다.

    db.query문에서 return을 callback(data)로 보내면 된다고 한다.

    // 해결된 구문
    var sample = function(callback){
    	db.query('SELECT * FROM topic', function(err, result){
          if(err) throw err;
          data = result.slice();
          return callback(data);
    	});
    };
    
    sample(function(data){
        console.log(data);    // 의도대로 출력된다!
    });

    callback 방식이 싫으면 동기식으로 작성할 수도 있고, 처리에 꼭 필요한 데이터라면 Promise 객체를 통해 실행여부를 명시해줄 수도 있다.

     

     

    [MySQL] 데이터베이스에 없는 id 처리하기: 값이 없으면 redirect

    데이터베이스에 없는 id를 조회하면 db.query() 조회값이 빈 배열([])이 되는데,

    그걸 모르고 배역 내 객체에서 프로퍼티를 호출하면

    Uncaught TypeError: undefined has no properties(or ~ is undefined) 에러가 뜬다.

     

    이런 경우를 방지하려면 db에 없는 id 조회를 시도할 때 예외처리를 해주어야 한다.

    나는 해당 페이지 홈으로 리다이렉트 하기로 했다.

    빈 배열이라면 object.length === 0으로 확인해도 되고, 필요한 프로퍼티가 undefined인지 확인해도 될 것 같다.

    나는 length로 확인했다.

    db.query('SELECT * FROM author WHERE id = ?', id, function(err, result){
      if (err) { throw err; };
      if (result.length === 0){
    	  // 리다이렉트 
      } else {
    	  // 여기서 나머지 작업을 실행하면 된다
      };
    });

     

     

    mysql: SELECT * FROM topic WHERE id =0=1 이 왜 호출이 되지?

    제일 당황했던 오류다. 테스트를 하다가 오타를 내서 ?id=0=1으로 조회를 했는데 ?id=0 페이지로 연결되는 것이다.

    알고보니 id는 MySQL DB에서 데이터타입이 integer여서, 정수가 아닌 정보들은 쓸모없으므로 전부 무시됐던 것.

    마찬가지로 id = 1@!%#!#@@321311^#$!을 입력해도 id = 1로 처리된다.

    [참고] stackoverflow - SQL ignoring string in where condition

     

    MySQL이 자체적으로 인풋 필터링을 해주기 때문에 데이터 조회시에 query.id를 그대로 쓰면

    URL에 ?id=2!@%!@#$ 같은 걸 입력해도 id 2번 데이터로 연결이 된다.

    이 id로 다른 걸 한다면 예상치못한 오류가 생길 수 있으니 입력을 받을 때 숫자만 남겨서 쓰는 게 좋겠다.

    var test = '14@#!231';
    var parsed = parseInt(test);
    console.log(parsed);  // 14

    *다만 parseInt()는 첫번째 non-whitespace 문자가 숫자로 변환되지 않는 경우 NaN을 반환한다.

    그래서 NaN인 경우 예외처리를 해줘야하는데, NaN === NaN 은 false기 때문에 Number.isNaN()을 써야한다.

     

    코드는 최종적으로 이렇게 수정되었다:

    var id = parseInt(url.parse(request.url, true).query.id);
      if (Number.isNaN(id)){
        id = 0;
    };

     

     

     

    [HTML] Form 데이터가 왜 잘려서 나오는 걸까?

    Author Update 중에 생긴 이슈다. 테스트할겸 {name: '테스트 1 2 3', profile: '테스트 1 2 3 !'}로 데이터를 입력해보았다.

    화면상에 표시는 잘 되는데 업데이트를 누르면 name: 테스트 까지만 표시되고 띄어쓰기 이후의 데이터가 잘렸다.

    이유는 태그의 value에 닫는 따옴표가 빠져서였다.

    <input type="text" name="name" placeholder="name" value=${data.name}>

    따옴표로 묶어주니 정상적으로 잘 출력된다.

    <input type="text" name="name" placeholder="name" value="${data.name}">

     

     

    [Node.js] Form - Delete 버튼 선택시 알림창 띄우기

    javaScript에서는 confirm('확인 메시지');로 Yes(true)/No(false)를 받을 수 있다.

    node.js에서는 어떻게 해야하는지 몰라서 undo 페이지를 따로 만들었는데 (post -> undo 페이지 -> 버튼으로 재확인)

    생활코딩 페이지 덧글에서 form 태그에 onsubmit 변수로 넣어주면 된다는 것을 발견했다!

    <form action="process_delete" method="post" onsubmit="return confirm('do you want to delete this file?')">
      <input type="hidden" name="id" value="${id}">
      <input type="submit" value="Delete" class="MenuEdit">
    </form>

    찾아보니 form의 onsubmit 변수가 form이 제출됐을 때 자바스크립트 구문을 실행하도록 제공되는 변수라고 한다.

    [W3 해당 페이지]

     

     

    [Node.js] 모듈 Export / Re-Export

    javaScript는 module.export를 아래와 같이 할 수 있다. 마지막 줄에서 몰아서 해도 된다.

    export { name1, name2, …, nameN };

    부모 모듈이 자식 모듈을 가져와서 다시 내보낼 수도 있다. [공식문서 참조]

    export foo from 'bar.js';

    상단에서 var module = require('path'); 형태로 임포트한 다음, 그 모듈을 다시 export 할 수도 있다.

    내 경우는 template을 menu, view, form 3가지로 나누어서 작성했는데, template 파일에서 이것들을 전부 모아서 새로 export 했다.

    불러와서 쓸 때는 template.menu.function 처럼 썼다.

    var menu = require('./template/menu');
    var view = require('./template/view');
    var form = require('./template/form');
    
    var getHTML = function(){
        console.log('HTML 작성용 함수');
    };
    
    module.exports = {
       menu, view, form, getHTML   // 위의 모듈들과 함께 export
    }

     

     

    [MySQL] 접속 실패 오류

    테스트 과정에서 MySQL 켜는 걸 잊어버렸더니 아래와 같은 에러가 떴다. service start mysql 잊지 말자.

    Error: connect ECONNREFUSED 127.0.0.1:3306
        at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1146:16)

     

     

    댓글

Designed by Tistory.