본문 바로가기
코딩과 알고리즘

구름IDE + Node.js + Rest API

코틀린 서버 연동 학습을 위해 단순한 Restful API 백앤드 서비스를 구름IDE에 준비해 보았습니다.
크레이가 가장 익숙한 언어는 PHP이지만 요즘 트랜드에 따라 Node.js 로 준비하였는데요.

관련 내용 공유드립니다.
무엇보다도 크레이 제 자신도 이 글을 다시 봤을 때 도움이 될거라는 확신을 가지고서 말이지요 :)

우선 이 글은 앞의 구름에듀에서 Node.js 를 설치하는 과정과

https://itadventure.tistory.com/577

 

구름IDE에서 node.js 사용기

파이스크립트 책자만 쓰다 보니 신선한 개발을 하지 못하는게 좀 답답해서 바람쐴겸(?) 한가지 시도를 해보았습니다 :) 바로 구름 IDE에서 node.js 서버를 써보는 것인데요. 강좌글이 아니라 체험기

itadventure.tistory.com

 

구름IDE에서 Mysql 설치까지 선제 진행된 것을 가정합니다.
이 과정까지는 진행이 되어야 그 다음 단계 진행이 가능합니다.

https://itadventure.tistory.com/598

 

구름IDE에 MYSQL 설치/활용

코틀린으로 앱개발 공부를 하면서 외부 서버 연동을 준비중입니다. 보통 안드로이드에서는 구글 파이어베이스 연동을 다루는 예제가 많은데요. 속도가 느린 문제가 있다고 하고 No-SQL 이 아닌 SQ

itadventure.tistory.com

그럼 오늘도 렛츠 고우~


구름 IDE를 사용할 경우는 유의할 부분이 있는데요.
구름IDE에서 지원하는 소스편집 기능을 사용하기 위해서는 소스를 아래 폴더 또는 그 하위 폴더에 위치하여야 합니다.

/workspace/가상서버명/

크레이의 경우 가상서버명이 crayNodeServer 인데요.
소스코드를 /workspace/crayNodeServer/ 하위 폴더에 위치해야만 편집이 가능하다는 것이지요.

크레이는 /workspace/crayNodeServer 폴더 하위에 rest 라는 폴더를 생성하여 Restful API를 구성하였습니다.

이를 위해 폴더를 생성하고, npm 초기화 및 필요한 모듈을 설치하는 과정이 필요한데요.
아래 스크립트 내용이 바로 그것입니다.

# cd /workspace/crayNodeServer
# mkdir rest
# cd rest
# npm install pm2 -g
# npm -y init
# npm install fs express ejs request body-parser mysql2 moment

2개의 클래스 소스1개의 서버소스로 구성하였습니다.
모듈로 구성해도 되지만 크레이는 클래스를 더 선호하는 편이라서요 ㅎ..

/workspace/crayNodeServer/rest 폴더를 기준으로 3개의 소스 내용은 아래와 같습니다.
오류처리도 없고 보안도 없는 빈약한 소스코드이지만 Restful 서비스의 CRUD 요소는 모두 가지고 있지요.
( POST - 생성하고, GET - 읽고, PUT - 고치고, DELETE - 지운다! )

/server.js ( 서버 소스 )

const express = require('express');
const request = require('request');
const bodyParser = require('body-parser');
const querystring = require('querystring');
const { cray7db } = require('./include/db');
const memberObject = require('./include/memberclass');

console.log(memberObject);

const app = express();
var fs = require("fs");

global.cray7db = cray7db;
global.memberObject = memberObject;

// cRud
app.get('/list', function (req, res) {
	(async () => { 
        try { 
            const data = await memberObject.list();
			
			await res.setHeader('Content-Type', 'application/json');
            await res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); 
            await res.end(JSON.stringify(data[0], null, '  ')); 
        } 
        catch (err) { 
            await res.json(err); 
        } 
    })();
})

// cRud
app.get('/:id', function (req, res) {
	(async () => { 
        try {
			await res.setHeader('Content-Type', 'application/json');
            await res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); 
			const data = await memberObject.get(req.params.id);
            await res.end(JSON.stringify(data[0], null, '  ')); 
        } 
        catch (err) { 
            await res.json(err); 
        } 
    })();
})

// Crud
app.post('/newuser', (req, res) => {
	(async () => { 
        try { 
			const data = await memberObject.newuser(
				req.query.member_id,
				req.query.passwd,
				req.query.nickname
			);
            await res.end(JSON.stringify({msg:'ok'}));
        } 
        catch (err) { 
            await res.json(err); 
        } 
    })();
})

// crUd
app.put('/edituser', (req, res) => {
	(async () => { 
        try { 
			const data = await memberObject.edituser(
				req.query.member_id,
				req.query.passwd,
				req.query.nickname
			);
            await res.end(JSON.stringify({msg:'ok'}));
        } 
        catch (err) { 
            await res.json(err); 
        } 
    })();
})

// cruD
app.delete('/deluser', (req, res) => {
	(async () => { 
        try { 
			const data = await memberObject.deleteuser(
				req.query.member_id
			);
            await res.end(JSON.stringify({msg:'ok'}));
        } 
        catch (err) { 
            await res.json(err); 
        } 
    })();
})


var server = app.listen(8081, function () {})

 

/include/db.js ( mysql 에 접속하는 DB 연결 래핑 소스 )

const mysql = require('mysql2/promise');    // mysql 프로미스 모듈 
const moment = require('moment'); // 현재시간 
const charset = 'utf8'; // DB 문자 세트 
const dbuser = 'cray7';    // DB 아이디  
const dbpass = '여러분의DB패스워드를 적어주세요'; // DB 패스워드 

// MYSQL 래핑 클래스 
class mysqllap { 
     
    constructor(opts) { 
        if(opts==undefined)return; 
        try { 
            this.pool = mysql.createPool(opts); 
        } 
        catch(err){ 
            console.log('error mysqllap::constructor'); 
            console.log(err); 
        } 
    } 
     
    async query(sql, args) { 
        try { 
            var sqlparse = await this.pool.format(sql, args);
			console.log(sqlparse)
            var data = await this.pool.query(sqlparse); 
			console.log(data)
            return await data; 
        } 
        catch(err){ 
            console.log('error mysqllap::query'); 
            console.log(err); 
            return await false; 
        } 
    }      
} 

const cray7db = new mysqllap({ 
  user: dbuser, password: dbpass, charset: charset, 
  supportBigNumbers : true, bigNumberStrings : true,
  host: '127.0.0.1', database: 'cray7db' 
}); 

module.exports = { cray7db };

 

/include/memberclass.js ( 회원관리 클래스 소스 )

// MYSQL 래핑 클래스 
class memberClass { 
     
    constructor(opts) { 
        
    } 
    
	// 회원정보 GET
    async get(member_id) { 
        return await cray7db.query( 
			"SELECT " +
			"idx, nickname, join_date, lastlogin_date, visit, point " +
			"FROM member WHERE member_id = ? LIMIT 1",
			[member_id]
		);       
    }      
	
	// 회원목록
	async list() { 
        return await cray7db.query( 
			"SELECT " +
			"idx, member_id, nickname, join_date, lastlogin_date, visit, point " +
			"FROM member ORDER BY idx;"
		);       
    }      
	
	// 신규회원
	async newuser(member_id, passwd, nickname){
		await cray7db.query( 
			"INSERT INTO member SET " +
			"member_id = ?, " + 
			"passwd = password(?), " +
			"nickname = ?, " + 
			"join_date = NOW(), " + 
			"lastlogin_date = NOW(), " + 
			"visit = 0, " + 
			"point = 0;",
			[member_id, passwd, nickname]
		);
	}
	
	// 회원 정보 수정
	async edituser(member_id, passwd, nickname){
		await cray7db.query( 
			"UPDATE member SET " +
			"passwd = password(?), " +
			"nickname = ? " + 
			"WHERE member_id = ?;",
			[passwd, nickname, member_id]
		);
	}
	
	// 회원 정보 삭제
	async deleteuser(member_id){
		await cray7db.query( 
			"DELETE FROM member " +
			"WHERE member_id = ?;",
			[member_id]
		);
	}
} 
memberObject = new memberClass();
module.exports = memberObject;


이제 위 소스를 구름IDE에서 작동하시려면 터미널에서 아래 명령어를 실행하면 되는데요.

node server.js

위 소스에서 포트번호는 8081이고, 서버 소스가 작동중이니 이제 테스트할 수 있을것 같지만 아직입니다.
지난 게시글처럼 구름IDE는 역시 포트번호를 그대로 사용할 수는 없구요.
포트포워딩을 외부에 열어주어야 합니다.

구름IDE 컨테이너 설정화면에 들어가

지난번과 비슷하게 8081이라는 포트번호를 추가하니 이번에는 53648이라는 포트번호가 할당되었네요.

이 경우 앤드포인트URL로 http://15.165.242.11:53648 을 사용할 수 있게 되었습니다.

한가지 더 준비되어야 할 부분이 있는데요.
바로 회원 테이블과 샘플데이터입니다.
MYSQL에 접속하여, 툴에서 입력하긴 했지만 쿼리문을 공유드리는게 간단해서,
테이블 생성문과 데이터 삽입 쿼리문을 공유드립니다.

CREATE TABLE `member` (
  `idx` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '일련번호',
  `member_id` varchar(20) CHARACTER SET utf8 DEFAULT '' COMMENT '회원아이디',
  `passwd` varchar(50) CHARACTER SET utf8 DEFAULT '' COMMENT '패스워드',
  `nickname` varchar(20) CHARACTER SET utf8 DEFAULT '' COMMENT '닉네임',
  `join_date` datetime DEFAULT NULL COMMENT '가입일자',
  `lastlogin_date` datetime DEFAULT NULL COMMENT '최종로그인',
  `visit` int(11) DEFAULT '0' COMMENT '방문횟수(1일1회집계)',
  `point` bigint(20) DEFAULT '0' COMMENT '포인트',
  PRIMARY KEY (`idx`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

cray, cray2, cray3 3명의 사용자 정보를 삽입하는 SQL문입니다.

insert into `member` (`idx`, `member_id`, `passwd`, `nickname`, `join_date`, `lastlogin_date`, `visit`, `point`) 
values('1','cray','','크레이','2022-12-26 23:08:32','2022-12-26 23:08:35','0','0');
insert into `member` (`idx`, `member_id`, `passwd`, `nickname`, `join_date`, `lastlogin_date`, `visit`, `point`) 
values('2','cray2','','크레이2','2022-12-26 23:08:54','2022-12-26 23:08:57','0','0');
insert into `member` (`idx`, `member_id`, `passwd`, `nickname`, `join_date`, `lastlogin_date`, `visit`, `point`) 
values('3','cray3','','크레이3','2022-12-26 23:09:11','2022-12-26 23:09:14','0','0');


이제 이렇게 데이터까지 입력했으니 REST API가 잘 작동하는지 확인해봐야겠지요.
우선 가장 기본적인 GET 동작은 웹브라우저에서 실행이 가능한데요.

웹브라우저에 아래와 같이 타이핑하니

http://13.209.255.71:53648/cray

아래와 같은 회원 1명의 JSON 데이터 결과물을 얻을 수 있었습니다.

[ { "idx": "1", "nickname": "크레이", "join_date": "2022-12-26T23:08:32.000Z", "lastlogin_date": "2022-12-26T23:08:35.000Z", "visit": 0, "point": "0" } ]

회원 목록도 동일한 GET  방식이기 때문에 아래와 같이 입력하면

http://13.209.255.71:53648/list

아래와 같은 JSON 데이터를 얻을 수 있는데요.

[ { "idx": "1", "member_id": "cray", "nickname": "크레이", "join_date": "2022-12-26T23:08:32.000Z", "lastlogin_date": "2022-12-26T23:08:35.000Z", "visit": 0, "point": "0" }, { "idx": "2", "member_id": "cray2", "nickname": "크레이2", "join_date": "2022-12-26T23:08:54.000Z", "lastlogin_date": "2022-12-26T23:08:57.000Z", "visit": 0, "point": "0" }, { "idx": "3", "member_id": "cray3", "nickname": "크레이3", "join_date": "2022-12-26T23:09:11.000Z", "lastlogin_date": "2022-12-26T23:09:14.000Z", "visit": 0, "point": "0" } ]

이렇게 생겨먹은 데이터를 가지고 코틀린에서 연동을 할 예정입니다.
관련 기술로 레트로핏인가가 가장 유명하더군요 ㅎㅎ

그 외에 회원정보를 추가하는 POST. 수정하는 PUT, 삭제하는 DELETE 등의 액션이 준비되어 있는데요.
이 3가지 액션은 웹브라우저에서 직접 테스트를 할 수가 없습니다.
그렇기 때문에 다른 방법으로 테스트를 해야 하는데요.
글이 길어지기 때문에 아쉽지만 그 방법은 다음에 공개드리도록 하겠습니다.

( 이제 잠자리로... 피곤하네요 ㅎㅎ )


그동안 블로그 방문횟수를 살펴보니 88만번이나 많은 분들이 블로그를 다녀가셨군요.
100만회 달성하면 이벤트라도 열어야 할것 같습니다 :)

오늘도 여전히 방문해주시는 모든 분들께 감사드립니다.
구독과 좋아요는 크레이의 탐구활동에 힘이 됩니다!