Skip to main content

24 posts tagged with "nodejs"

View All Tags

· 2 min read

triple des 알고리즘으로 암호화하는 일은 요새는 드문데, 드물어서 그런지 구글링해도 아무 것도 나오지 않았다.

레거시 언어들에는 3des 암호화된 로직이 많은데, 포팅하면서 개발해야될 필요성이 생겼다.

암호화

  • nodejs 의 암호화 패키지는 crypto 안에 들어있다.
  • crypto.getCiphers(); 메소드로 사용할 수 있는 알고리즘을 확인할 수 있다.
    • ciphers.filter(cipher => cipher.includes('des'));
  • 3des 알고리즘은 des-ede3 로 시작한다.
  • 키는 24bytes 이며, iv 는 8bytes 다.

소스

const crypto = require("crypto");

class TripleDes {
// #iv;
// #key;

constructor(key, iv) {
this.key = key;
this.iv = iv;
}

getKey() {
return this.key.padEnd(24, String.fromCharCode(0));
}

getIv() {
return this.iv;
}

encrypt(plain, iv) {
if (!iv) {
iv = this.iv ? Buffer.from(this.iv, "hex") : crypto.randomBytes(8);
}

const cipher3des = crypto.createCipheriv(
"des-ede3-cfb8",
this.getKey(),
iv
);
let encrypted = cipher3des.update(plain, "utf8", "hex");
encrypted += cipher3des.final("hex");

this.iv = iv.toString("hex");
return encrypted;
}

decrypt(encrypted, iv) {
if (!iv) {
iv = this.iv;
}

const decipher3des = crypto.createDecipheriv(
"des-ede3-cfb8",
this.getKey(),
Buffer.from(iv, "hex")
);
let decrypted = decipher3des.update(encrypted, "hex", "utf8");
decrypted += decipher3des.final("utf8");

return decrypted;
}
}

module.exports = TripleDes;

위의 encrypt 메소드는 아래처럼 버퍼를 합친 후에 헥스로 바꾸는 것과 동일하다.

let encrypted = Buffer.concat([
cipher3des.update(plain, "utf8"),
cipher3des.final("utf8"),
]);

encrypted = encrypted.toString("hex");

사용법

const TripleDes = require("./TripleDes");

const tripleDes = new TripleDes("encryptionKey");
// 암호화
const encrypted = tripleDes.encrypt("yummy");
// 복호화
const decrypted = tripleDes.decrypt(encrypted);

참조

· 15 min read

잊기 전에 슈도코드를 정리해놓자.

슈도코드

var 전체모듈 = [
function () {
const 합계함수 = (a, b) => a + b;
return 합계함수;
},

function () {
const 내부_합계함수 = 전체모듈[0]();
const 합계 = 내부_합계함수(10, 20);
console.log(합계);
return 합계;
},
];

let 시작모듈_인덱스 = 1;
전체모듈[시작모듈_인덱스]();

해석

배열에 다 때려넣고 호출해서 사용하는 방법이다 물론 내부는 더 복잡하다, 코드 스플리팅이 된다면 더더욱.

복잡한 내부

https://github.com/hg-pyun/minipack-kr/blob/master/src/minipack.js
/**
* @source https://github.com/hg-pyun/minipack-kr/blob/master/src/minipack.js
*
* 모듈 번들러들은 작은 코드 조각들을 웹 브라우저에서 실행될 수 있는 크고 복잡한 파일로 컴파일합니다.
* 이 작은 조각들은 단지 자바스크립트 파일들일 뿐이며, 이들 사이의 종속성은 모듈 시스템에 의해 표현됩니다
* (https://webpack.js.org/concepts/modules).
*
* 모듈 번들러들은 entry file 이라는 개념을 가지고 있습니다. 브라우저에 스크립트 태그를 몇개 추가하여
* 실행하는 대신, 번들 담당자에게 응용 프로그램의 메인 파일이 무엇인지 알려 줍니다. 이 파일이 어플리케이션을
* 실행하는 진입점이 됩니다.
*
* 번들러는 entry file의 의존성을 분석합니다. 그리고 그 다음 파일의 의존성을 파악합니다.
* 이 작업은 애플리케이션의 모든 모듈과 각 모듈이 서로 어떻게 의존하는지 파악할 때까지 반복됩니다.
*
* 이러한 프로젝트에 대한 이해를 종속성 그래프라 부릅니다.
*
* 이 예제에서는 종속성 그래프를 만들고 이 그래프를 사용하여 모든 모듈들을 하나의 번들로 패키징 합니다.
* 그럼 시작해 보겠습니다 :)
*
* 참고: 이 예제는 매우 단순화되어 있습니다. 순환 참조, 캐싱 모듈, 파싱 최적화 등에 대한 내용은 생략
* 하여 가능한가 단순하게 만들었습니다.
*/

const fs = require("fs");
const path = require("path");
const babylon = require("babylon");
const traverse = require("babel-traverse").default;
const { transformFromAst } = require("babel-core");

let ID = 0;

// 우선 file path를 받는 함수를 생성하고
// 파일을 내용을 읽고, 종속성을 추출합니다.
function createAsset(filename) {
// 파일의 내용을 문자열로 읽습니다.
const content = fs.readFileSync(filename, "utf-8");

// 이제 이 파일이 어떤 파일에 종속되는지 알아보겠습니다. 우리는 import 문자열을 보고 의존성을
// 파악할 수 있습니다 하지만, 이것은 단순한 접근법이어서, 대신에 자바스크립트 파서를 사용하겠습니다.

// 자바스크립트 파서들은 자바스크립트 코드를 읽고 이해할 수 있도록 도와주는 툴입니다.
// 파서는 AST(abstract syntax tree)라는 좀더 추상화된 모델을 생성합니다.
//
// AST에 대해 이해하려면 AST Explorer(https://astexplorer.net)을 꼭 보기를 강력하게 추천합니다.
// AST가 어떻게 이루어져 있는지 확인할 수 있습니다.
//
// AST는 우리의 코드에 대해 많은 정보를 가지고 있습니다. 우리는 쿼리를 이용하여
// 우리의 코드가 하려는 일에 대해 이해할 수 있습니다.
const ast = babylon.parse(content, {
sourceType: "module",
});

// 이 배열은 현재 모듈의 의존성을 상대 경로로 가지고 있을 것입니다.
const dependencies = [];

// 우리는 AST 순회를 통해 각각의 모듈들이 어떤 의존성을 가지고 있는지 이해하려 합니다.
// 이것을 통해 AST안에서 모든 import keyword 선언을 파악할 수 있습니다.
traverse(ast, {
// ECMAScript 모듈들은 정적이므로 매우 파악하기 쉽습니다.이는 변수를 가져올 수 없거나 조건부로
// 다른 모듈을 가져올 수 없음을 의미합니다. import 구분을 볼 때 마다 카운팅을 하고 의존성을 가지고
// 있는 것으로 간주 할 수 있습니다.
ImportDeclaration: ({ node }) => {
// import 구문마다 dependencies 배열에 값을 추가합니다.
dependencies.push(node.source.value);
},
});

// 또한 간단한 카운터를 이용하여 이 모듈에 고유 식별자를 할당합니다.
const id = ID++;

// 우리는 일부 브라우저에서만 지원하는 ECMAScript module들이나 기능들을 사용할 가능성도 있습니다.
// 우리가 만드는 번들이 모든 브라우저에서 돌아가도록 Babel을 이용해서 transpile할 수 있습니다
// (https://babeljs.io 참고).
//
// `presets` 옵션은 Babel이 어떻게 우리 코드를 바꿀지에 대해 결정합니다. 우리는 `babel-preset-env`
// 를 이용하여 대부분의 브라우저에서 우리의 코드를 사용할 수 있도록 바꾸도록 하겠습니다.
const { code } = transformFromAst(ast, null, {
presets: ["env"],
});

// 이 모듈에 대한 정보를 return 합니다.
return {
id,
filename,
dependencies,
code,
};
}

// 이제 단일 모듈의 종속성을 추출할 수 있으므로, entry file의 의존성을 추출하는 것부터 시작하겠습니다.
// 이 작업은 애플리케이션의 모든 모듈과 각 모듈이 서로 어떻게 의존하는지를 파악할 때까지 계속 진행할 것입니다.
// 이 작업의 결과물을 의존성 그래프라 부릅니다.
function createGraph(entry) {
// entry file부터 분석을 시작합니다.
const mainAsset = createAsset(entry);

// queue를 사용해서 모든 asset의 의존성을 분석하도록 하겠습니다. 이 작업을 위해
// entry asset을 가지고 있는 배열을 정의합니다.
const queue = [mainAsset];

// 여기서 queue의 반복을 위해 `for ... of` 반복문을 사용합니다. 처음에는 queue가 asset을 하나만
// 가지고 있지만 작업이 반복되는 동안에 새로운 asset들을 queue에 추가합니다. 이 반복문은 queue가
// 비어질 때 까지 계속됩니다.
for (const asset of queue) {
// 모든 asset들은 의존성이 있는 모듈에 대한 상대경로들을 리스트로 가지고 있습니다. 우리는 그 리스트를
// 순회하면서 `createAsset()`함수로 분석하고, 아래 객체를 통하여 모듈들의 의존성을 추척할 것입니다.
asset.mapping = {};

// 이것은 이 모듈이 있는 디렉토리입니다.
const dirname = path.dirname(asset.filename);

// 종속성에 대한 상대 경로 리스트를 순회합니다.
asset.dependencies.forEach((relativePath) => {
// `createAsset()` 함수는 절대 경로가 필요합니다. dependencies 배열은 상대 경로를 가지고
// 있는 배열입니다. 이러한 경로들은 모듈이 import된 file에 따라 상대적입니다. 따라서 부모 asset의
// 경로를 이용해서 상대 경로를 절대경로로 바꿔야 합니다.
const absolutePath = path.join(dirname, relativePath);

// asset의 내용울 분석하고, 내용을 읽고, 의존성을 추출합니다.
const child = createAsset(absolutePath);

// `asset`의 의존성은 `child`에게 달려있습니다. 우리는 `mapping` 객체에 relativePath와 child.id를
// 이용해서 관계를 표현할 수 있습니다.
asset.mapping[relativePath] = child.id;

// 마지막으로 child asset을 queue에 추가하여 구문 분석이 반복되도록 합니다.
queue.push(child);
});
}

// 이 시점에서 queue는 애플리케이션의 모든 모듈이 포함된 배열입니다. 이것이 우리가 그래프를 표현하는 방법입니다.
return queue;
}

// 다음으로, 그래프를 이용하여 브라우저에서 실행할 수 있는 번들을 반환하는 함수를 정의합니다.
//
// 우리의 번들은 self-invoking(자신을 부를수 있는)함수를 가지고 있습니다.
//
// (function() {})()
//
// 이 함수는 하나의 인자만 받을 수 있습니다: 모든 모듈의 정보를 가지고 있는 그래프.
function bundle(graph) {
let modules = "";

// 이 함수를 구성하기 전에 매개 변수로 전달할 객체를 만들겠습니다. 반드시 알아둬야할 것은 우리가 만드는
// 스트링은 2개의 중괄호({})로 감싸져 있어야 한다는 것입니다. 우리는 다음과 같은 포멧으로 추가할
// 것입니다: `key: value,`.
graph.forEach((mod) => {
// 그래프안에 있는 모든 모듈들은 entry를 객체로 가지고 있습니다. 우리는 module의 id를
// 값에 대한 키로 사용합니다.(각 모듈마다 2개의 값이 있습니다.)
//
// 찻번째 값은 함수로 감싼 각 모듈의 코드입니다. 그 이유는 모듈의 scope를 지정해야 하기 때문입니다.
// 한 모듈에서 변수를 정의하면 다른 모듈이나 글로벌 scope에 영향을 주지 않아야 합니다.
//
// transpiled된 모듈들은 CommonJS 모듈 시스템을 사용합니다:
// 해당 모듈 시스템은 `require`, `module`, 그리고 `exports`를 통해 모듈화 합니다.
// 이 키워드들은 일반적으로 브라우저에서 사용할수 없으므로, 우리의 함수를 이용하여 주입해야 합니다.
//
// 두번째 값은 모듈간의 의존성 매핑을 stringify하는 것입니다. 다음과 같은 객체입니다.
// { './relative/path': 1 }.
//
// transpiled된 우리의 모듈들이 상대경로와 합께 `require()`를 호출하기 때문입니다. 이 함수를 호출하면
// 그래프에서 이 모듈의 상대 경로에 해당하는 모듈을 확인할 수 있습니다.
modules += `${mod.id}: [
function (require, module, exports) { ${mod.code} },
${JSON.stringify(mod.mapping)},
],`;
});

// 마지막으로 self-invoking 함수의 body를 만듭니다.
//
// `require()` 함수를 만들며 시작하겠습니다: 모듈 id를 받아 앞서 만든 모듈 오브젝트에서 `module`을
// 찾습니다. function wrapper와 맵핑 객체를 얻기위해 two-value 객체를 이용합니다.
//
// 모듈의 코드는 모듈의 id들 대신 상대경로와 함께 `require()`함수를 호출합니다. 우리가 만든 require 함수는
// id 받습니다. 또한 두개의 모듈은 동일한 상대 경로를 요구할 수 있지만, 실제론 두개의 다른 모듈들을
// 의미하게 됩니다.
//
// 이 문제를 해결하기 위해 별도의 `require` 함수를 제공합니다. 모듈의 맵핑 오브젝트를 이용하여 상대경로를 ids에 할당합니다.
// 맵핑 오브젝트는 구체적인 모듈을 가져오기 위한 용도로, 상대 경로와 모듈 ids를 맵핑합니다.
//
// 마지막으로, 모듈이 require 되었을 때 exports 객체의 값이 노출되어야 합니다. 따라서 모듈 코드에 의해 변환 된
// `exports` 객체는 `require()`로 반환됩니다.
const result = `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports : {} };
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})
`;

// 결과를 반환합니다. 만세! :)
return result;
}

const graph = createGraph("./example/entry.js");
const result = bundle(graph);

console.log(result);

여기의 번역된 내용을 확인해보자.

· One min read

express에 있는 sendStatus 기능을 활용하기 위해 start/hooks.js에도 다음 로직을 추가하자

소스

start/hooks.js
const { hooks } = require("@adonisjs/ignitor");

hooks.after.providersBooted(() => {
const Response = use("Adonis/Src/Response");

Response.macro("sendStatus", function (status) {
this.status(status).send("");
});
});

추가 후엔 controller에서 response.sendStauts(403)과 같은 응답을 반환할 수 있다

· One min read

git pull을 hook으로 실행하는데 계속 nginx 502 gateway timeout 오류가 발생해 pm2 logs 앱 또는 nginx log를 계속 추적해도 별다른 에러 로그가 없었다. (exec: Interal Server error 라고만 적혀있었다.)

삽질 끝에 watch 속성을 사용하고 있는게 문제였다.

해결

ecosystem.config.js
module.exports = {
apps: [
{
name: "server",
script: "server.js",
env_production: {
NODE_ENV: "production",
},
watch: true,
ignore_watch: ["node_modules", ".git", "yarn.lock", "package-lock.json"],
exec_mode: "cluster",
instances: "max",
},
],
};

ignore_watch 속성에 .git 폴더와 다른 폴더들이 제대로 예외처리 되었는지 확인해보자.

· 2 min read

utf8 charset 에서는 이모지를 처리할 수 없다. utf8mb4 언어셋 소개 및 표현범위 포스팅에 따르면 이모지가 mysql 또는 maria 의 utf8 셋의 가변공간을 사용하려 들려 하는 문제인데, utf8mb4 셋으로 변경하면 해결 된다.

mysql 이거나 maria 의 charset 을 변경하기 힘들 때 text 를 모두 replace 치면 된다. 시작해보자

php

writing a simple removeEmoji function stackoverflow 글에 좋은 함수가 있다.

소스

<?php
// https://stackoverflow.com/questions/12807176/php-writing-a-simple-removeemoji-function
function removeEmoji($text) {
$clean_text = "";
// Match Emoticons
$regexEmoticons = '/[\x{1F600}-\x{1F64F}]/u';
$clean_text = preg_replace($regexEmoticons, '', $text);
// Match Miscellaneous Symbols and Pictographs
$regexSymbols = '/[\x{1F300}-\x{1F5FF}]/u';
$clean_text = preg_replace($regexSymbols, '', $clean_text);
// Match Transport And Map Symbols
$regexTransport = '/[\x{1F680}-\x{1F6FF}]/u';
$clean_text = preg_replace($regexTransport, '', $clean_text);
// Match Miscellaneous Symbols
$regexMisc = '/[\x{2600}-\x{26FF}]/u';
$clean_text = preg_replace($regexMisc, '', $clean_text);
// Match Dingbats
$regexDingbats = '/[\x{2700}-\x{27BF}]/u';
$clean_text = preg_replace($regexDingbats, '', $clean_text);
// Match Flags
$regexDingbats = '/[\x{1F1E6}-\x{1F1FF}]/u';
$clean_text = preg_replace($regexDingbats, '', $clean_text);
// Others
$regexDingbats = '/[\x{1F910}-\x{1F95E}]/u';
$clean_text = preg_replace($regexDingbats, '', $clean_text);
$regexDingbats = '/[\x{1F980}-\x{1F991}]/u';
$clean_text = preg_replace($regexDingbats, '', $clean_text);
$regexDingbats = '/[\x{1F9C0}]/u';
$clean_text = preg_replace($regexDingbats, '', $clean_text);
$regexDingbats = '/[\x{1F9F9}]/u';
$clean_text = preg_replace($regexDingbats, '', $clean_text);
return $clean_text;
}
<?php
$textWithEmoji = 'thumbs up👍👍';
$text = removeEmoji($textWithEmoji);

// text => thumbs up

node

emoji-regex 라이브러리를 쓰면 된다.

$ yarn add emoji-regex

소스

const emojiRegex = require("emoji-regex");

// const regex = emojiRegex()
const textWithEmoji = "thumbs up👍👍";
const text = textWithEmoji.replace(emojiRegex(), "");

// text => thumbs up

여담

이모지처리 (붙히고 제거하고), linkify 를 합친 통합 모듈이 있다면 다운로드 수가 좀 될 것 같은데.

· 2 min read

아도니스는 로깅 모듈로 winston 을 사용하는데 winston 은 로그를 json 포맷으로 출력하고 심지어 timezone 변수가 따로 나온다.

로그는 기본적으로 프로젝트 폴더 하위의 tmp 폴더에 생성된다 이 폴더가 없으면 로깅이 되지 않으므로 먼저 만들어주자

예를 들면 다음과 같다.

{"level":"info","message":"serving app on http://127.0.0.1:3333","timestamp":"2017-12-21T05:34:50.235Z"}
{"level":"info","message":"serving app on http://127.0.0.1:3333","timestamp":"2017-12-21T05:45:32.220Z"}

이걸 다음과 같이 바꿔보자

[2017-12-21 15:01:03.506] INFO serving app on http://127.0.0.1:3333

수정

오늘 날짜를 구하기 위해 moment를 먼저 설치하자. winston 패키지는 @adonisjs/framework 패키지에 종속된다.

config/app.js
const moment = use("moment");
const { config } = use("winston");

module.exports = {
logger: {
transport: "file",
file: {
driver: "file",
name: "adonis-app",
// 파일명을 바꿔준다
filename: `adonis_${moment().format("YYYYMMDD")}.log`,
level: "debug",
// json 로그 포맷을 해제하고
json: false,
// 원하는 형태로 바꿔준다
formatter: ({ level, message, meta }) => {
const now = moment().format("YYYY-MM-DD HH:mm:ss.SSS");
// 로그 레벨에 색상을 추가하는 작업인데, 굳이 필요하진 않다
const logLevel = config.colorize(level, level.toUpperCase());
const formattedMeta =
meta && Object.keys(meta).length ? "\n\t" + JSON.stringify(meta) : "";

return `[${now}] ${logLevel} ${message || ""} ${formattedMeta}`;
},
},
},
};

버전

  • @adonis/framework: 4.0.28
  • winston: 2.4.0

· 8 min read

nodejs로 정말 간단한 oauth2, jwt 등의 인증을 사용하지 않는 API를 만들기에는 express가 정말 딱이다.

하지만 조금 더 깔끔한 코드를 원하거나 새롭지만 반복적인 기능을 넣기 위해선 더 큰 프레임워크가 필요했다.

조건

언제나 개발시간은 부족하기에 내가 필요한 기능들을 정리해 보았다.

  • await, async를 아무 세팅 없이 사용해 Promise chaining에서 벗어나고 싶다. (BabelWebpack을 직접 세팅하는 시간 걸리는 짓을 안하고 싶다.)
  • 기본적인 웹 보안기능 (validate, csrf, xss, injection) 및 API Throttle Request 기능이 있어야한다.
  • 소셜 로그인passport를 사용하지 않고 한 줄로 끝내고 싶었다.
  • JWT 인증, Log 기능, Bcrypt 암호화를 직접 붙히고 싶지 않다.
  • ORM에 맞게 E-commerce DB를 구조화했기 때문에 ORM이 꼭 필요하다.
  • 직관적이여야 한다. (Model과 Controller가 완벽히 나뉘는 구조를 원했다.)
  • 문서화가 완벽해야한다.
  • 커뮤니티Github Issue가 활성화 되어있어야한다.
  • 관리자 기능을 SSR로 해야할 가능성이 있어 템플릿 기능이 있어야하며, ejs는 절대 쓰고 싶지 않고 다른 템플릿을 직접 세팅해야하는 일은 더더욱 없어야한다.

이 조건을 거르니 adonisjs 라는 처음 보는 프레임워크가 눈앞에 있었다.

성능

image from hexo

나쁘지 않다. 어차피 nginx에서 reverse proxy 처리에, auto scaling 걸꺼고 그래도 더 빠른 속도를 원하면 golang으로 가야지 왜 이 포스트를 보고있나!

  • 여기서 자세한 내용을 확인할 수 있다.

평판

이 프레임워크를 쓰는 다른 사람은 어떻게 생각할까도 궁금했다.

Node.js broken ecosystem and rise of AdonisJs란 포스팅이 마음을 사로잡았다. 원문 읽을 시간이 없다면 이 글의 요약은 이 것이다.

Why not use Express + Sequelize + Config manager + Passport + dotEnv + NodeMailer + Node Validator +30 other modules. How assembling 100+ modules manually can be better than a beautifully pre-configured framework?

한글로 번역하면 왜 안 쓰시죠? 정도가 되시겠다.

시작하기

요구사항

  • Node >= 8.0
  • NPM >= 3.0

1711 기준 8.9가 LTS 버전이기 때문에 그냥 쓰면 된다.

Cli 설치

요즘 멋진 프레임워크들은 다들 cli를 가지고 있다.

$ yarn global add @adonisjs/cli

앱 설치

$ adonis new 프로젝트명
$ cd 프로젝트명
$ adonis serve --dev

localhost:3333에서 서버가 돌아간다. image from hexo

폴더 구조

.
├── ace
├── package.json
├── public
├── server.js
└── start
├── app.js
├── kernel.js
└── routes.js
파일/폴더설명
aceadonis에서 제공하는 cli 명령어 툴
package.json-
publiccss, js, images와 같은 public 리소스
server.jsHTTP 서버를 부트스트래핑한다. .env 파일의 PORT 변수를 따른다.
start/app.jsadonis 실행시 필요한 기능들을 부트스트래핑한다.
start/kernel.js미들웨어를 등록한다.
start/routes.js라우팅

라우팅

start/routes.js에서 등록한다.

메소드

Route 뒤에 method를 붙히면 된다.

Route.get("/boards", async () => {});

Route.get("/boards/:id", async ({ params }) => {
const board = await Board.find(params.id);
return board;
});

컨트롤러와 연결

Route.get("boards", "BoardController.index");
/* 리소스로 사용시에 */
Route.resource("boards", "BoardController");

/* 리소스에서 create, edit 메소드를 빼고 사용시 */
Route.resource("boards", "BoardController").apiOnly();

/* 구미가 당기는 것만 사용시 */
Route.resource("boards", "BoardController").only(["index", "destroy"]);

/* 구미가 안 당기는 것을 제외할 시 */
Route.resource("boards", "BoardController").except(["index", "destroy"]);

그룹화

보통은 기능이 비슷한 것 끼리 그룹화를 해서 가독성을 높인다.

/* /api/boards routes */
Route.group(() => {
Route.get("/boards", "BoardController.index");
Route.post("/boards", "BoardController.store");
}).prefix("api");

컨트롤러

cli에서 쉽게 생성이 가능하다.

$ adonis make:controller BoardController

리소스 컨트롤러

리소스가 대단한 건 아니고 미리 정의 된 메소드 7개로 RESTful API를 빠르게 만들기 위한 것이다.

$ adonis make:controller BoardController --resource

각 메소드들이 연결되는 건 다음과 같다.

  • index: GET boards
  • create: GET boards/create
  • store: POST boards
  • show: GET boards/:id
  • edit: GET boards/:id/edit
  • update: PUT boards/:id
  • destory: DELETE boards/:id

모델

하나의 모델은 하나의 테이블과 매칭된다고 보면 된다.

$ adonis make:model Board

모델 구조

모델 생성 후에 몇 가지 설정을 해줘야한다.

Board.js
class Board extends Model {
// 테이블 명을 변경해야할 경우
// (테이블 명이 모델명의 복수형이 아닐 경우)
static get table() {
return "board";
}

// 기본 커넥션이 변경될 경우
static get connection() {
return "mysql";
}

// PK 컬럼명이 id가 아닐 경우
static get primaryKey() {
return "uid";
}

// PK의 Auto Increment가 아닐 경우
static get incrementing() {
return false;
}

// 비밀번호 같은 컬럼을 보여주지 않아야 될 경우
// 이 경우 모델에서 fetch 또는 first 메소드로 쿼리빌더를 실행해야된다
static get hidden() {
return ["password"];
}

// 생성일 컬럼이 변경될 경우
static get createdAtColumn() {
return "created_at";
}

// 업데이트일 컬럼이 변경될 경우
static get updatedAtColumn() {
return "updated_at";
}
}

위 속성을 제외하고는 문서를 참조해보자.

기존 코드를 내려받을 때

.env.example 파일을 .env로 복사해 환경설정을 해주고 로그를 남기기 위해 tmp 폴더를 생성해준다

/
$ cp .env.example .env
# .env 파일을 수정하고

$ mkdir tmp

여담

이 프레임워크는 라라벨 스타일로 만들어졌기 때문에 (개념이 같다) 모던 PHP 개발자가 되는 건 식은 죽 먹기가 될 수 있다.

· 2 min read

최근 소스들을 까보다가 발견했는데, npm에 등록된 패키지를 CDN으로 바로 활용 가능한 서비스가 있다. 바로 unpkg이다. 리젼은 여기서 확인 가능하다.

사용

사용법은 엄청나게 간단하다. node_modules 폴더 안에 있는 구조를 URL에 그대로 입력만 해주면 된다. 몇 가지 예로 확인해보자.

<!-- React -->
<script src="https://unpkg.com/react@15.3.1/dist/react.min.js"></script>
<!-- Lodash -->
<script src="https://unpkg.com/lodash@4.17.4/lodash.min.js"></script>
<!-- Bootstrap css -->
<script src="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css"></script>

node_modules 안의 경로가 생각나지 않는다 라면 폴더 경로까지만으로 웹에서 접근해보자. 예를 들어 Bootstrap4라면 https://unpkg.com/bootstrap@4.0.0-alpha.6/ 까지만 들어가면 폴더 구조가 보인다.

여담

블로그의 스크립트도 unpkg로 바꿨는데, 로딩속도가 1초 가까이 줄어든 느낌이다.

이슈

unpkg로 스크립트를 가져오는 도중에 503 timeout 에러가 발생해서 블로그가 동작하지 않았었다. 같은 방법으로 동작하지만 좀 더 reliable한 서비스인 jsdelivr를 사용하자.

· 3 min read

Bower의 시대가 끝났다. 홈페이지를 들어가보면 다음과 같은 문구가 보인다.

..psst! While Bower is maintained, we recommend yarn and webpack for new front-end projects!

Yarn을 사용해보자.

설치

2020년에는 npm 사용을 추천드립니다. 더 이상 느리지 않습니다.

NPM으로 설치할 수도 있는데 추천하는 방법은 Installer이니 다운받고 설치해주면 된다! NPM으로 설치시에는 환경변수 등록을 거쳐야한다.

설치 후 Bash에서 확인해보자.

$ yarn --version
0.24.5

사용법

npm 사용법과 아주 유사하다. 기존 NodeJS 패키지에서 yarn 명령어만을 입력하면 완벽히 호환이 되고, 새로운 프로젝트라면 yarn init 명령어를 실행하면 된다. package.json을 사용하기 때문에 그냥 명령어만 바뀌었다고 생각하면 된다. (패키지들도 npm의 것을 공유한다.)

명령어 비교

install이 add로, uninstall이 remove로, update가 upgrade로 바뀐게 사실상 끝이다. 자세한 옵션은 CLI Docs를 참조하자.

npmYarn
npm installyarn install
(N/A)yarn install --flat
(N/A)yarn install --har
(N/A)yarn install --no-lockfile
(N/A)yarn install --pure-lockfile
npm install [package](N/A)
npm install --save [package]yarn add [package]
npm install --save-dev [package]yarn add [package][--dev/-D]
(N/A)yarn add [package][--peer/-P]
npm install --save-optional [package]yarn add [package][--optional/-O]
npm install --save-exact [package]yarn add [package][--exact/-E]
(N/A)yarn add [package][--tilde/-T]
npm install --global [package]yarn global add [package]
npm update --globalyarn global upgrade
npm rebuildyarn install --force
npm uninstall [package](N/A)
npm uninstall --save [package]yarn remove [package]
npm uninstall --save-dev [package]yarn remove [package]
npm uninstall --save-optional [package]yarn remove [package]
npm cache cleanyarn cache clean
rm -rf node_modules && npm installyarn upgrade

Global 경로

  • Windows: %LOCALAPPDATA%/Yarn/config/global

환경 변수 설정

설정을 확인한 뒤 prefix 경로를 PATH에 추가해주면 된다.

$ yarn config list
{ prefix: 'C:\\Users\\{NAME}\\npm' }

결론

bower_components 안녕 이젠 node_modules만 있겠구나