본문으로 건너뛰기

"nodejs" 태그로 연결된 26개 게시물개의 게시물이 있습니다.

모든 태그 보기

타입스크립트에서 json import 방법

· 약 1분

TS5071

node 에서 즐겨쓰는 package.json import 방법은 아래와 같다.

import packageJson from "../package.json";
console.log(packageJson.version);

편안하게 잘 사용되는 로직인데 타입스크립트로 변경시에는 몇 가지 설정을 해줘야한다. 설명에 필요없는 설정은 생략했다.

tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true
}
}

또는 tsc 실행시에 --esModuleInterop, --resolveJsonModule 옵션을 추가해 빌드해줘야한다.

참조

puppeteer 크롤링 속도 증가시키기

· 약 1분

페이지를 가져온 뒤 css, image, font를 차단하면 더 빠른 DOM 액세스가 가능하다.

리소스 차단

// @types/puppeteer
import { launch, Browser, Request, Page } from "puppeteer";

const browser: Browser = await launch({
headless: true,
});

const page: Page = await browser.newPage();
await page.setRequestInterception(true);

page.on("request", (req: Request) => {
switch (req.resourceType()) {
case "stylesheet":
case "font":
case "image":
req.abort();
break;
default:
req.continue();
break;
}
});

await page.goto("URL");

nodejs triple des 암호화

· 약 2분

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

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

암호화

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

소스

import crypto from "node: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분

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

슈도코드

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

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

const 시작모듈_인덱스 = 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);

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

adonisjs에 response.sendStatus 추가하기

· 약 1분

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)과 같은 응답을 반환할 수 있다

pm2 앱에서 git hook시에 nginx 502 gateway timeout 오류

· 약 1분

원인

git pull을 hook으로 실행하는데 계속 nginx 502 gateway timeout 오류가 발생해 pm2 logs 앱 또는 nginx log를 계속 추적해도 별다른 에러 로그가 없었다. (exec: Internal 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 폴더와 다른 폴더들이 제대로 예외처리 되었는지 확인해보자.

utf8 charset에서 emoji 필터링하기

· 약 2분

개요

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 를 합친 통합 모듈이 있다면 다운로드 수가 좀 될 것 같은데.

adonisjs 로그 포맷 변경하기 (custom log format)

· 약 2분

아도니스는 로깅 모듈로 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

adonisjs 시작하기 (nodejs framework)

· 약 8분

개요

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 개발자가 되는 건 식은 죽 먹기가 될 수 있다.