프로그래밍/General

(5) RESTful API 활용하기 - 웹편

Lou Park 2017. 1. 26. 17:04

글 목차

(1) Node.js로 RESTful API 만들기 - POST편

(2) Node.js로 RESTful API 만들기 - GET편

(3) Node.js로 RESTful API 만들기 - PUT편

(4) Node.js로 RESTful API 만들기 - DELETE편

현재글 >> (5RESTful API 활용하기 - 웹편

(6) RESTful API 활용하기 - 안드로이드 앱편 (작성중)


이제 앞선 강의에서 만든 API 서버를 가지고 웹에서 요청을 날려볼 것이다.

그러면 우선 웹을 만들어야 한다. 보통은 html을 만들지만, 이 강의에서는 쉽고 빠르게 작성하기 위해 pug 뷰 엔진을 사용하도록 하겠다.


프로젝트가 있는 경로에서 아래 명령어를 이용해 pug를 설치하자.

npm install pug


app.js

그 다음 app.js를 다음과 같이 바꾸어 준다.

자세한 설명은 주석에 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var express = require("express");
var mysql = require("mysql");
var bodyParser = require("body-parser");
var config = require("./config"); // config.js
var path = require("path");
var pug = require("pug");
var app = express();
 
// bodyParser는 미들웨어이기 때문에 라우터 보다 항상 위에 있도록 해야함
app.use(bodyParser.json());     
// parse application/x-www-form-urlencoded 
app.use(bodyParser.urlencoded({extended: false}));
 
// 앱의 뷰 엔진을 pug로 설정
app.set("view engine""pug");
// view 파일 (html) 들이 있는 경로 설정
// 여기서는 ./public/views/~
app.set("views", path.join(__dirname, "public""views"));
 
// Mysql DB pool 생성
var pool = mysql.createPool({
            // config.js에 있는 정보를 바탕으로 연결
            host: config.mysql.host,
            port: config.mysql.port,
            user: config.mysql.username,
            password: config.mysql.password,
            database: config.mysql.db,
            connectionLimit:20,
            waitForConnections:false
        });
 
// Main
app.listen(config.port, function() {
    console.log("Server listening on port %d", config.port);
});
 
// Router
// 기본으로 index.js를 찾기 때문에 
// require("./routes/index.js")라고 명시해주지 않았음
var routes = require("./routes")(app, pool);
var apiRoutes = require("./routes/api")(app, pool);
 
cs


파일 경로

app.js에서 view파일이 ./public/views 경로 아래에 있다고 말해놓았기 때문에

그 경로를 실제로 만들어 주어야 한다.


mkdir public

cd public

mkdir views

cd views

touch index.pug


위의 명령어를 사용하거나 그냥 직접 만들어주면된다.

전체 파일 구조는 아래 사진과 같다.




api.js

api.js? 이상한게 보인다.

api.js는 기존에 있던 index.js를 복사해서 라우트 경로만 /api/를 더 추가해 준 코드다.

index.js는 이제 웹에서 쓸 것들을 모을거고, api는 api.js에 넣어놓았다. 전체 코드는 아래와 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
var async = require("async");
var mysql = require("mysql");
 
module.exports = function(app, pool) {
    // >> POST
    app.post("/api/games"function(req, res) {
        var result = {};
        var title = null;
        async.waterfall([
        function(callback) {
            title = mysql.escape(req.body.title);
            callback();
        },
        function(callback) {
            if (title == undefined) {
                callback(new Error("Title is empty."));
            } else {
                // db에 연결하여 sql 수행
                pool.getConnection(function(err, conn) {
                    // title 정보를 DB에 넣기 위한 SQL문 준비
                    var sql = "INSERT INTO myGames (title) VALUES (" + title + ");";
                    console.log("SQL: " + sql);
                    conn.query(sql, function(err) {
                        if (err) {
                            // err가 떠도 conn은 release() 꼭 해주어야한다.
                            conn.release();
                            callback(err);
                        } else {
                            conn.release();
                            callback();
                        }
                    });
                });
            }
        }],
        function(err) {
            result = returnResult(err, res)
            result.status = res.statusCode;
            res.send(result);
        });
    });
 
    // >> GET
    app.get("/api/games"function(req, res) {
        var result = {};
        // db에 연결하여 sql 수행
        pool.getConnection(function(err, conn) {
            var sql = "SELECT * from myGames;";
            conn.query(sql, function(err, rows) {
                var result = returnResult(err, res);
                if (rows) {
                    result.message = rows;
                }
                conn.release();
                result.status = res.statusCode;
                //res.send(result);
                // games라는 이름으로 결과들을 index.pug로 넘김
                res.render("index", { games: rows });
            });
        });
    });
 
    // >> GET/id
    app.get("/api/games/:id"function(req, res) {
        var result = {};
        // SQL injection attack 방지위해 mysql.escape();
        var id = mysql.escape(req.params.id);
        
        // db에 연결하여 sql 수행
        pool.getConnection(function(err, conn) {
            var sql = "SELECT * from myGames WHERE id=" + id + ";";
            conn.query(sql, function(err, rows) {
                var result = returnResult(err, res);
                if (rows) {
                    result.message = rows;
                }
                conn.release();
                result.status = res.statusCode;
                res.send(result);
            });
        });
    });
 
    // >> PUT
    app.put("/api/games/:id"function(req, res) {
        var result = {};
        var id = null;
        var title = null;
        async.waterfall([
        function(callback) {
            id = mysql.escape(parseInt(req.params.id));
            title = mysql.escape(req.body.title);
            callback();
        },
        function(callback) {
            if (id == undefined) {
                callback(new Error("Id is empty."));
            } else if (title == undefined) {
                callback(new Error("Title is empty."));
            } else {
                // db에 연결하여 sql 수행
                pool.getConnection(function(err, conn) {
                    // title 정보를 업데이트 하기 위한 SQL
                    var sql = "UPDATE myGames SET title=" + title + " WHERE id=" + id + ";";
                    console.log("SQL: " + sql);
                    conn.query(sql, function(err) {
                        if (err) {
                            // err가 떠도 conn은 release() 꼭 해주어야한다.
                            conn.release();
                            callback(err);
                        } else {
                            conn.release();
                            callback();
                        }
                    });
                });
            }
        }],
        function(err) {
            result = returnResult(err, res)
            result.status = res.statusCode;
            res.send(result);
        });
    });
 
    // >> DELETE
    app.delete("/api/games/:id"function(req, res) {
        var result = {};
        var id = null;
 
        async.waterfall([
        function(callback) {
            id = mysql.escape(parseInt(req.params.id));
            callback();
        },
        function(callback) {
            if (id == undefined) {
                callback(new Error("Id is empty."));
            } else {
                // db에 연결하여 sql 수행
                pool.getConnection(function(err, conn) {
                    var sql = "DELETE FROM myGames WHERE id=" + id + ";";
                    conn.query(sql, function(err) {
                        if (err) {
                            // err가 떠도 conn은 release() 꼭 해주어야한다.
                            conn.release();
                            callback(err);
                        } else {
                            conn.release();
                            callback();
                        }
                    });
                });
            }
        }],
        function(err) {
            result = returnResult(err, res)
            result.status = res.statusCode;
            res.send(result);
        });
    });
}
 
var returnResult = function(err, res) {
    // 결과를 눈으로 보기 쉽게하기 위해 result 객체 생성
    var result = {};
    if (err) {
        res.status(400);
        result.message = err.stack;
    } else {
        res.status(200);
        result.message = "Success";
    }
    return result;
}
cs


index.js

index.js에서는 request 라이브러리를 사용해서 api.js 에있는 라우터를 호출 했다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var request = require("request");
 
var apiUrl = "http://localhost:3000/api/games"
 
module.exports = function(app, pool) {
    // >> Index page
    app.get("/"function(req, res) {
        // GET 메소드 /games로 넘어감
        res.redirect("/games");
    });
 
    // >> 게임 목록 (홈)
    app.get("/games"function(req, res) {
        request(apiUrl, function (err, response, result) {
            if (!err && response.statusCode == 200) {
                // api에서 get으로 받아온 결과를 JSON으로 변환, 메세지만 가져옴
                var gameData = JSON.parse(result).message;
                // games라는 이름으로 결과들을 index.pug로 넘김
                res.render("index", { games: gameData });
            }
        });
    });
}
 
cs

index.pug
디자인은 최소화! 순정 HTML로만 작업해 놓았다.
pug를 처음 보시는 분들은 약간 혼란 스러울 수 있으나
글쓴이도 pug를 처음 사용해 보았기 때문에 너무 이상하게 생긴거 같다고 걱정하지 말길 바란다...
앞서 index.js에서 games라는 이름으로 게임들 데이터를 넘겨주었는데, 그걸 그대로 받아 뿌려준다.
game객체는 id/title 값을 가지기 때문에, game.id, game.title 이렇게 두개의 값을 골라 사용할 수 있다.

그리고 jQuery와 Ajax를 통해 API 서버와 몇가지 작업을 한다.
사실 Ajaxe도 처음이다...원래 새로고침 하는게 아니라 부분만 업데이트 해주는 걸로 아는데 
몰라서 그냥 새로고침 했다. (아시는 분 답글 부탁드립니다 ㅠㅠ)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
doctype html
html
    head
        title 내가 해 본 게임들
        // JQuery 사용
        script(src="https://code.jquery.com/jquery-3.1.1.min.js")
    body
        h3 해본 게임 목록
        ul
            // pug의 반복문
            each game in games
                // 받은 게임 중에서도 title 속성 값만을 뿌린다.
                li= game.title
                    button(class="button-delete" id="button-delete-" + game.id type="submit") 삭제
                    form(id="form-change-" + game.id)
                        input(id="input-change-" + game.id type="text")
                        button(class="button-change" id="button-change-" + game.id type="submit") 이름 변경
        hr
        h3 게임 등록
        input(id="input-post" type="text")
        button(id="button-post" type="submit") 등록하기
 
    // 여기서 부터는 script, public/javascript/만들어서 관리 해도됨
    script.
        // PUT 메소드
        $(".button-change").click(function() {
            // id값 가져오기
            var game_id = $(this).attr("id").split("-")[2];
            var nameToChange = $("#input-change-" + game_id).val();
            if (nameToChange.length < 1) {
                alert("바꿀 이름을 입력하세요.");
            } else {
                // 전송할 JSON Data 만들기
                var gameData = {
                    "title": nameToChange
                }
                $.ajax( {
                    type: "PUT",
                    data: gameData,
                    url: "/api/games/" + game_id,
                    dataType: "JSON"
                }).done(function(res) {
                    // 성공시 폼 초기화
                    if (res.status == 200) {
                        $("#input-change-" + game_id).val("");
                    } else {
                        alert("이름 변경에 실패하였습니다.");
                    }
                });
            }
        });
 
        // POST 메소드
        $("#button-post").click(function() {
            var title = $("#input-post").val();
            if (title.length < 1) {
                alert("등록할 게임 이름을 입력하세요.");
            } else {
                // 전송할 JSON Data 만들기
                var gameData = {
                    "title": title
                }
                $.ajax( {
                    type: "POST",
                    data: gameData,
                    url: "/api/games/",
                    dataType: "JSON"
                }).done(function(res) {
                    if (res.status == 200) {
                        // 새로고침
                        window.location.reload();
                    } else {
                        alert("게읻 등록에 실패하였습니다.");
                    }
                });
            }
        });
 
        // Delete 메소드
        $(".button-delete").click(function() {
            var game_id = $(this).attr("id").split("-")[2];
            $.ajax( {
                type: "DELETE",
                url: "/api/games/" + game_id,
            }).done(function(res) {
                if (res.status == 200) {
                    window.location.reload();
                } else {
                    alert("게임 삭제에 실패하였습니다.");
                }
            });
        });
 
 
cs

실행해 보면 깔끔하니 이쁘게 생긴(?) 페이지가 하나 뜰 것이다.

여기서 get, put, delete, post 모든 작업을 할 수 있다.


별거 아닌 듯 보였는데 웹을 정말 몰라서 힘든 작업이었다. ㅠㅠ

다음엔 android로 API 서버와 통신을 해 보겠다.