Citizen은 Node의 내장 주위를 파거나 50 개의 다른 패키지로 만든 흔들리는 Jenga 타워를 함께 모은 대신 빠르고 확장 가능한 웹 사이트를 신속하게 구축하는 데 관심이있는 사람들을 위해 설계된 MVC 기반 웹 응용 프로그램 프레임 워크입니다.
시민을 기존 서버 측 웹 애플리케이션, 모듈 식 단일 페이지 응용 프로그램 (SPA) 또는 편안한 API의 기초로 사용하십시오.
0.9.x에서 1.0.x 로의 전환에는 수많은 파괴 변화가있었습니다. 항목 별 목록의 ChangEleg를 참조 하고이 업데이트 된 문서를 철저히 검토하십시오.
분명히 이것은 NPM/Github readme가 포함 해야하는 것보다 더 많은 내용입니다. 이 문서를위한 사이트에서 작업하고 있습니다.
개인 사이트와 OriginalTrilogy.com에서 시민을 사용합니다. OT.com은 단일 시민 사례를 운영하는 $ 30의 클라우드 호스팅 계획에서 적당한 양의 트래픽 (매월 수십만 개의 조회)을 처리합니다. 여기서 앱/프로세스는 충돌없이 한 번에 몇 달 동안 실행됩니다. 매우 안정적입니다.
이 명령은 웹 앱에 대한 새 디렉토리를 만들고 Citizen을 설치하고 스캐 폴딩 유틸리티를 사용하여 앱의 골격을 만들고 웹 서버를 시작합니다.
$ mkdir myapp && cd myapp
$ npm install citizen
$ node node_modules/citizen/util/scaffold skeleton
$ node app/start.js모든 것이 잘 진행되면 웹 서버가 실행중인 콘솔에서 확인이 표시됩니다. 브라우저에서 http://127.0.0.1:3000으로 이동하면 Bare Index 템플릿이 표시됩니다.
Citizen은 기본 템플릿 엔진에서 템플릿 리터럴을 사용합니다. Consolidate를 설치하고 템플릿 구성을 업데이트하며 그에 따라 기본보기 템플릿을 수정할 수 있습니다.
구성 옵션은 구성을 참조하십시오. 더 많은 유틸리티가 시작하는 데 도움이되는 유틸리티를 보려면 유틸리티를 참조하십시오.
app/
config/ // These files are all optional
citizen.json // Default config file
local.json // Examples of environment configs
qa.json
prod.json
controllers/
hooks/ // Application event hooks (optional)
application.js
request.js
response.js
session.js
routes/ // Public route controllers
index.js
helpers/ // Utility modules (optional)
models/ // Models (optional)
index.js
views/
error/ // Default error views
404.html
500.html
ENOENT.html
error.html
index.html // Default index view
start.js
logs/ // Log files
access.log
error.log
web/ // public static assets
시민을 수입하고 앱을 시작하십시오.
// start.js
import citizen from 'citizen'
global . app = citizen
app . start ( )터미널에서 실행 :
$ node start.js구성 파일, 시작 옵션 및/또는 사용자 정의 컨트롤러 구성으로 Citizen 앱을 구성 할 수 있습니다.
구성 디렉토리는 선택 사항이며 Citizen과 앱을 모두 구동하는 JSON 형식의 구성 파일을 포함합니다. 이 디렉토리 내에 여러 Citizen 구성 파일을 가질 수 있으므로 환경에 따라 다른 구성이 가능합니다. Citizen은 다음 계층을 기반으로 구성을 구축합니다.
host 키를 찾는 각 JSON 파일을 구문 분석하고 파일 구성으로 기본 구성을 확장합니다.host 키를 찾을 수없는 경우 Citizen.json이라는 파일을 찾고 해당 구성을로드합니다.로컬 데브 환경에서 포트 8080에서 Citizen을 실행하고 싶다고 가정 해 앱이 연결할 로컬 데이터베이스가 있다고 가정 해 봅시다. local.json (또는 dev.json, 원하는대로)이라는 구성 파일을 다음과 같이 만들 수 있습니다.
{
"host" : "My-MacBook-Pro.local" ,
"citizen" : {
"mode" : "development" ,
"http" : {
"port" : 8080
}
} ,
"db" : {
"server" : "localhost" , // app.config.db.server
"username" : "dbuser" , // app.config.db.username
"password" : "dbpassword" // app.config.db.password
}
}이 구성은 로컬 컴퓨터에서 실행할 때만 기본 구성을 확장합니다. 이 방법을 사용하면 다른 환경의 여러 구성 파일을 동일한 저장소에 커밋 할 수 있습니다.
데이터베이스 설정은 app.config.db 통해 앱 내 어디에서나 액세스 할 수 있습니다. citizen 및 host 노드는 프레임 워크를 위해 예약되어 있습니다. 자신의 노드를 만들어 사용자 정의 설정을 저장하십시오.
app.start() 를 통해 시작에서 앱의 구성을 설정할 수 있습니다. 구성 파일이 있으면 시작 구성이 구성 파일을 확장합니다. 구성 파일이 없으면 시작 구성이 기본 시민 구성을 확장합니다.
// Start an HTTPS server with a PFX file
app . start ( {
citizen : {
http : {
enabled : false
} ,
https : {
enabled : true ,
pfx : '/absolute/path/to/site.pfx'
}
}
} ) 경로 컨트롤러 레벨에서 사용자 정의 구성을 설정하려면 config 객체를 내보내십시오 (경로 컨트롤러 및 경로 컨트롤러 섹션의 동작에 대한 자세한 내용).
export const config = {
// The "controller" property sets a configuration for all actions in this controller
controller : {
contentTypes : [ 'application/json' ]
}
// The "submit" property is only for the submit() controller action
submit : {
form : {
maxPayloadSize : 1000000
}
}
} 다음은 시민의 기본 구성을 나타내며 구성에 의해 확장됩니다.
{
host : '' ,
citizen : {
mode : process . env . NODE_ENV || 'production' ,
global : 'app' ,
http : {
enabled : true ,
hostname : '127.0.0.1' ,
port : 80
} ,
https : {
enabled : false ,
hostname : '127.0.0.1' ,
port : 443 ,
secureCookies : true
} ,
connectionQueue : null ,
templateEngine : 'templateLiterals' ,
compression : {
enabled : false ,
force : false ,
mimeTypes : [
'application/javascript' ,
'application/x-javascript' ,
'application/xml' ,
'application/xml+rss' ,
'image/svg+xml' ,
'text/css' ,
'text/html' ,
'text/javascript' ,
'text/plain' ,
'text/xml'
]
} ,
sessions : {
enabled : false ,
lifespan : 20 // minutes
} ,
layout : {
controller : '' ,
view : ''
} ,
contentTypes : [
'text/html' ,
'text/plain' ,
'application/json' ,
'application/javascript'
] ,
forms : {
enabled : true ,
maxPayloadSize : 524288 // 0.5MB
} ,
cache : {
application : {
enabled : true ,
lifespan : 15 , // minutes
resetOnAccess : true ,
encoding : 'utf-8' ,
synchronous : false
} ,
static : {
enabled : false ,
lifespan : 15 , // minutes
resetOnAccess : true
} ,
invalidUrlParams : 'warn' ,
control : { }
} ,
errors : 'capture' ,
logs : {
access : false , // performance-intensive, opt-in only
error : {
client : true , // 400 errors
server : true // 500 errors
} ,
debug : false ,
maxFileSize : 10000 ,
watcher : {
interval : 60000
}
} ,
development : {
debug : {
scope : {
config : true ,
context : true ,
cookie : true ,
form : true ,
payload : true ,
route : true ,
session : true ,
url : true ,
} ,
depth : 4 ,
showHidden : false ,
view : false
} ,
watcher : {
custom : [ ] ,
killSession : false ,
ignored : / (^|[/\]).. / // Ignore dotfiles
}
} ,
urlPath : '/' ,
directories : {
app : < appDirectory > ,
controllers : < appDirectory > + '/controllers',
helpers : < appDirectory > + '/helpers',
models : < appDirectory > + '/models',
views : < appDirectory > + '/views',
logs : new URL('../../../logs', import.meta.url).pathname
web : new URL('../../../web', import.meta.url).pathname
}
}
} 다음은 시민의 환경과 그들이하는 일에 대한 완전한 요약입니다.
서버를 시작할 때 Citizen의 http 및 https 구성 옵션 외에도 Node의 http.createserver () 및 https.createserver ()와 동일한 옵션을 제공 할 수 있습니다.
유일한 차이점은 키 파일을 전달하는 방법입니다. 위의 예에서 볼 수 있듯이 Citizen은 키 파일의 파일 경로를 통과시킵니다. Citizen은 귀하를 위해 파일을 읽습니다.
| 환경 | 유형 | 기본값 | 설명 |
|---|---|---|---|
host | 끈 | '' | 다른 환경에서 다른 구성 파일을로드하려면 Citizen은 서버의 호스트 이름을 키로 사용합니다. 시작시 Citizen이 서버의 호스트 이름과 일치하는 host 키가있는 구성 파일을 찾으면 해당 구성 파일을 선택합니다. 이것은 HTTP 서버 hostname 과 혼동되지 않아야합니다 (아래 참조). |
| 시민 | |||
mode | 끈 | NODE_ENV 먼저 확인하고 그렇지 않으면 production 확인합니다 | 응용 프로그램 모드는 특정 런타임 동작을 결정합니다. 가능한 값은 production 및 development 생산 모드 침묵 콘솔 로그입니다. 개발 모드는 Verbose 콘솔 로그, URL 디버그 옵션 및 핫 모듈 교체를 가능하게합니다. |
global | 끈 | app | 시작 파일에서 시민을 초기화하기위한 컨벤션은 프레임 워크를 글로벌 변수에 할당합니다. 문서 전체에서 참조 할 수있는 기본값은 app 입니다. 다른 이름을 사용하려면이 설정을 변경할 수 있습니다. |
contentTypes | 정렬 | [ 'text/html', 'text/plain', 'application/json', 'application/javascript' ] | 클라이언트의 Accept 요청 헤더를 기반으로 각 요청에 대한 응답 형식의 허용 목록. 개별 경로 컨트롤러 또는 동작에 사용 가능한 형식을 구성 할 때 사용 가능한 전체 배열을 제공해야합니다. |
errors | 끈 | capture | 응용 프로그램에 오류가 발생하면 기본 동작은 시민이 오류에서 복구하고 응용 프로그램을 계속 실행하려고 시도하는 것입니다. 이 옵션을 exit 하도록 설정하면 Citizen에게 오류를 기록하고 대신 프로세스를 종료하도록 지시합니다. |
templateEngine | 끈 | templateLiterals | Citizen은 기본적으로 View 렌더링을 위해 [템플릿 리터럴] (https://developer.mozilla.org/en-us/web/javaScript/reference/template_literals) 구문을 사용합니다. 선택적으로, 통합을 설치하고 지원하는 엔진을 설치하고 사용할 수 있습니다 (예 : 핸들 바 설치 및 templateEngine handlebars 설정). |
urlPath | 끈 | / | 앱으로 이어지는 URL 경로를 나타냅니다. http://yoursite.com/my/app을 통해 앱에 액세스 할 수 있고 다른 서버를 프론트 엔드로 사용하지 않으려면 요청을 프록시로 사용하지 않으려면이 설정은 /my/app 이어야합니다 (주요 슬래시를 잊지 마십시오). 이 설정은 라우터가 작동하는 데 필요합니다. |
| http | |||
enabled | 부울 | true | HTTP 서버를 활성화합니다. |
hostname | 끈 | 127.0.0.1 | HTTP를 통해 앱에 액세스 할 수있는 호스트 이름. 모든 호스트 이름에서 요청을 수락 할 빈 문자열을 지정할 수 있습니다. |
port | 숫자 | 3000 | Citizen의 HTTP 서버가 요청을 듣는 포트 번호. |
| https | |||
enabled | 부울 | false | HTTPS 서버를 활성화합니다. |
hostname | 끈 | 127.0.0.1 | HTTPS를 통해 앱에 액세스 할 수있는 호스트 이름. 기본값은 LocalHost이지만 모든 호스트 이름에서 요청을 수락 할 빈 문자열을 지정할 수 있습니다. |
port | 숫자 | 443 | Citizen의 HTTPS 서버가 요청을 듣는 포트 번호. |
secureCookies | 부울 | true | 기본적으로 HTTPS 요청 내에서 설정된 모든 쿠키는 안전합니다. 이 옵션을 false 로 설정하여 해당 동작을 무시하고 모든 쿠키를 불안하게 만들고 쿠키 지시문에서 secure 옵션을 수동으로 설정해야합니다. |
connectionQueue | 정수 | null | 대기열에 대한 수신 요청의 최대 수. 지정되지 않은 상태로 유지하면 운영 체제는 큐 제한을 결정합니다. |
| 세션 | |||
enabled | 부울 | false | 각 방문자에게 고유 ID를 할당하고 응용 프로그램 서버 내에 해당 ID와 관련된 데이터를 저장할 수있는 사용자 세션 범위를 활성화합니다. |
lifespan | 긍정적 인 정수 | 20 | 세션이 활성화되면이 숫자는 사용자 세션의 길이를 몇 분 안에 나타냅니다. 사용자 가이 시간 동안 활동하지 않은 경우 세션이 자동으로 만료됩니다. |
| 공들여 나열한 것 | |||
controller | 끈 | '' | 글로벌 레이아웃 컨트롤러를 사용하는 경우 모든 컨트롤러에서 next 지침을 사용하는 대신 해당 컨트롤러의 이름을 지정할 수 있습니다. |
view | 끈 | '' | 기본적으로 레이아웃 컨트롤러는 기본 레이아웃보기를 사용하지만 여기에서 다른 뷰를 지정할 수 있습니다. 파일 확장자없이 파일 이름을 사용하십시오. |
| 형태 | |||
enabled | 부울 | true | Citizen은 간단한 형태의 기본 페이로드 처리를 제공합니다. 별도의 양식 패키지를 사용하려면 이것을 false 로 설정하십시오. |
maxPayloadSize | 긍정적 인 정수 | 524288 | 바이트의 최대 양식 페이로드 크기. 양식 입력 데이터로 서버가 과부하되지 않도록 최대 페이로드 크기를 설정하십시오. |
| 압축 | |||
enabled | 부울 | false | 렌더링 된 뷰 및 정적 자산에 대한 GZIP 및 DEFLATE 압축을 활성화합니다. |
force | 부울 또는 끈 | false | 압축 형식을 수락하는 것을보고하지 않더라도 모든 클라이언트에 대한 GZIP 또는 DEFLATE 인코딩을 강제합니다. 많은 프록시와 방화벽은 GZIP 지원을 결정하는 수락 인코딩 헤더를 깨뜨 리며 모든 현대 고객이 GZIP를 지원하기 때문에 gzip 로 설정하여 강제로 강제로 강제 deflate 할 수 있습니다. |
mimeTypes | 정렬 | 위의 기본 구성을 참조하십시오. | 압축이 활성화되면 압축 될 MIME 유형의 배열. 기본 목록은 위의 샘플 구성을 참조하십시오. 항목을 추가하거나 제거하려면 배열 전체를 교체해야합니다. |
| 은닉처 | |||
control | 키/값 쌍을 포함하는 객체 | {} | 이 설정을 사용하여 경로 컨트롤러 및 정적 자산에 대한 캐시 제어 헤더를 설정하십시오. 핵심은 자산의 경로 이름이고 값은 캐시 제어 헤더입니다. 자세한 내용은 클라이언트 측 캐싱을 참조하십시오. |
invalidUrlParams | 끈 | warn | Route Cache 옵션은 잘못된 URL 매개 변수를 지정하여 불량 URL이 캐시되는 것을 방지 할 수 있으며 invalidUrlParams 잘못된 URL을 만나거나 클라이언트 측 오류를 던질 때 경고를 기록할지 여부를 결정합니다. 자세한 내용은 캐싱 요청 및 컨트롤러 작업을 참조하십시오. |
| CACHE.APPLICATION | |||
enabled | 부울 | true | cache.set() 및 cache.get() 메소드를 통해 액세스 된 메모리 캐시를 활성화합니다. |
lifespan | 숫자 | 15 | 캐시 된 애플리케이션 자산의 시간은 몇 분 안에 메모리에 남아 있습니다. |
resetOnAccess | 부울 | true | 캐시에 액세스 할 때마다 캐시 된 자산에서 캐시 타이머를 재설정할지 여부를 결정합니다. false 로 설정되면 lifespan 에 도달하면 캐시 된 항목이 만료됩니다. |
encoding | 끈 | utf-8 | 파일 경로를 cache.set ()로 전달하면 인코딩 설정에 따라 파일을 읽을 때 인코딩을 사용해야합니다. |
synchronous | 부울 | false | 파일 경로를 cache.set ()로 전달하면이 설정에서 파일을 동기식으로 또는 비동기 적으로 읽어야하는지 여부가 결정됩니다. 기본적으로 파일 읽기는 비동기입니다. |
| cache.static | |||
enabled | 부울 | false | 정적 파일을 제공 할 때 Citizen은 일반적으로 각 요청에 대한 디스크에서 파일을 읽습니다. 이를 메모리의 파일 버퍼를 캐시하는 true 로 설정하여 정적 파일을 상당히 높이면 속도를 높일 수 있습니다. |
lifespan | 숫자 | 15 | 캐시 된 정적 자산의 시간은 몇 분 안에 메모리에 남아 있습니다. |
resetOnAccess | 부울 | true | 캐시에 액세스 할 때마다 캐시 된 정적 자산에서 캐시 타이머를 재설정할지 여부를 결정합니다. false 로 설정되면 lifespan 에 도달하면 캐시 된 항목이 만료됩니다. |
| 로그 | |||
access | 부울 | false | HTTP 액세스 로그 파일을 활성화합니다. 액세스 로그가 빠르고 이상적으로는 웹 서버에서 처리해야하므로 기본적으로 비활성화됩니다. |
debug | 부울 | false | 디버그 로그 파일을 활성화합니다. 생산 문제를 디버깅하는 데 유용하지만 매우 장점 (개발 모드의 콘솔에서 볼 수있는 동일한 로그). |
maxFileSize | 숫자 | 10000 | 로그 파일의 최대 파일 크기를 킬로바이트로 결정합니다. 한계에 도달하면 로그 파일의 이름이 타임 스탬프로 바뀌고 새 로그 파일이 생성됩니다. |
| logs.error | |||
client | 부울 | true | 400 레벨 클라이언트 오류의 로깅이 가능합니다. |
server | 부울 | false | 500 레벨 서버/애플리케이션 오류 로깅을 활성화합니다. |
status | 부울 | false | 제작 모드에있을 때 상태 메시지를 콘솔에 로그인 해야하는지 여부를 제어합니다. (개발 모드는 항상 콘솔에 기록됩니다.) |
| logs.watcher | |||
interval | 숫자 | 60000 | 파일 이벤트를 지원하지 않는 운영 체제의 경우이 타이머는 아카이브 전에 변경에 대한 로그 파일이 밀리 초로 얼마나 자주 투표 될지 결정합니다. |
| 개발 | |||
| Development.debug | |||
scope | 물체 | 이 설정은 개발 모드에서 디버그 출력에 어떤 스코브가 기록 된 지 결정합니다. 기본적으로 모든 스코프가 활성화됩니다. | |
depth | 긍정적 인 정수 | 3 | Citizen이 디버그 내용에 객체를 버리면 Node의 Util.inspect를 사용하여 검사합니다. 이 설정은 검사의 깊이를 결정합니다. 이는 검사 및 표시 될 노드 수를 의미합니다. 더 많은 숫자는 더 깊은 검사와 성능이 느린 것을 의미합니다. |
view | 부울 | false | Debug 정보를 HTML보기에 직접 덤프하도록 TRUE로 설정하십시오. |
enableCache | 부울 | false | 개발 모드는 캐시를 비활성화합니다. 개발 모드에서 캐시를 활성화하려면이 설정을 true 로 변경하십시오. |
| 개발. 워처 | |||
custom | 정렬 | Citizen의 핫 모듈 교체에 대해 자신의 사용자 정의 모듈을 볼 수 있습니다. 이 배열에는 watch (App Directory 내의 모듈에 대한 상대 디렉토리 경로)가있는 개체가 포함될 수 있으며 (이 모듈을 할당하는 변수) 속성을 assign . 예:[ { "watch": "/util", "assign": "app.util" } ] | |
Citizen은 Chokidar를 파일 감시자로 사용하므로 로그 및 개발 모드 모두에 대한 watcher 옵션은 Chokidar가 허용하는 옵션도 허용합니다.
이 설정은 app.config.host 및 app.config.citizen 통해 공개적으로 노출됩니다.
이 문서는 글로벌 앱 변수 이름이 app 이라고 가정합니다. 그에 따라 조정하십시오.
app.start() | 시민 웹 응용 프로그램 서버를 시작합니다. |
app.config | 시작시 제공된 구성 설정. 시민의 설정은 app.config.citizen 내에 있습니다. |
app.controllersapp.modelsapp.views | 컨트롤러와보기에 직접 액세스 할 필요는 없지만 모델을 수동으로 가져 오는 대신 Citizen의 내장 핫 모듈 교체로 수동으로 이점을 가져 오는 대신 app.models . 모들 참조 모드를 참조하십시오. |
app.helpers | app/helpers/ 에 배치 된 모든 도우미/유틸리티 모듈은 Helpers Object로 가져옵니다. |
app.cache.set()app.cache.get()app.cache.exists()app.cache.clear() | 애플리케이션 캐시 및 키/가치 저장소 Citizen이 내부적으로 사용하여 앱에 사용할 수 있습니다. |
app.log() | Citizen이 사용하는 기본 콘솔 및 파일 로깅, 귀하의 사용을 위해 내보내기. |
Citizen URL 구조는 어떤 경로 컨트롤러 및 조치를 해제하고, URL 매개 변수를 전달하며, 고유 식별자로 두 배가 될 수있는 SEO 친화적 인 컨텐츠를위한 공간을 결정합니다. 구조는 다음과 같습니다.
http://www.site.com/controller/seo-content/action/myAction/param/val/param2/val2
예를 들어, 사이트의 기본 URL이 다음과 같이 가정 해 봅시다.
http://www.cleverna.me
기본 경로 컨트롤러는 index 이고 기본 조치는 handler() 이므로 위는 다음과 같습니다.
http://www.cleverna.me/index/action/handler
article 경로 컨트롤러가있는 경우 다음과 같은 요청을 할 것입니다.
http://www.cleverna.me/article
쿼리 문자열 대신 Citizen은 이름/값 쌍으로 구성된 URL 매개 변수를 전달합니다. 237의 기사 ID와 2 페이지의 페이지 번호를 통과 해야하는 경우 이름/값 쌍이 URL에 추가됩니다.
http://www.cleverna.me/article/id/237/page/2
유효한 매개 변수 이름에는 문자, 숫자, 밑줄 및 대시가 포함될 수 있지만 문자 나 밑줄로 시작해야합니다.
기본 컨트롤러 동작은 handler() 이지만 action 매개 변수로 대체 작업을 지정할 수 있습니다 (나중에 자세히 설명).
http://www.cleverna.me/article/action/edit
Citizen은 또한 선택적으로 관련 콘텐츠를 URL에 URL에 삽입 할 수 있습니다.
http://www.cleverna.me/article/My-Clever-Article-Title/page/2
이 SEO 컨텐츠는 항상 컨트롤러 이름을 따라야하며 컨트롤러 동작을 포함하여 이름/값 쌍보다 우선합니다. route.descriptor 통해 또는 url 범위 내 (이 경우 url.article )를 통해 일반적으로 액세스 할 수 있습니다. 즉, 고유 식별자로 사용할 수 있습니다 (Route Controllers 섹션의 URL 매개 변수에 대한 자세한 내용).
URL 매개 변수 action 및 direct 프레임 워크에 예약되어 있으므로 앱에 사용하지 마십시오.
시민은 간단한 모델 뷰 컨트롤러 컨벤션에 의존합니다. 위에서 언급 한 기사 패턴은 다음 구조를 사용할 수 있습니다.
app/
controllers/
routes/
article.js
models/
article.js // Optional, name it whatever you want
views/
article.html // The default view file name should match the controller name
주어진 URL에는 하나 이상의 경로 컨트롤러가 필요하며 Route Controller의 기본보기 파일은 이름을 공유해야합니다. 모델은 선택 사항입니다.
주어진 경로 컨트롤러의 모든 뷰는 app/views/ 디렉토리에 존재하거나 클리너 조직의 컨트롤러의 이름과 일치하는 디렉토리에 배치 될 수 있습니다.
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html // The default view
edit.html // Alternate article views
delete.html
보기 섹션의보기에 대한 자세한 내용.
모델과 뷰는 선택 사항이며 반드시 특정 컨트롤러와 관련 될 필요는 없습니다. 경로 컨트롤러가 추가 처리 및 최종 렌더링을 위해 출력을 다른 컨트롤러로 전달하는 경우 일치하는 뷰를 포함 할 필요가 없습니다 (컨트롤러 Next Directive 참조).
Citizen Route 컨트롤러는 JavaScript 모듈입니다. 각 경로 컨트롤러는 요청 된 경로에 대한 조치 역할을하기 위해 하나 이상의 내보내기가 필요합니다. 기본 조치는 handler() 라는 이름이어야하며 URL에 조치가 지정되지 않은 경우 Citizen이 호출합니다.
// Default route controller action
export const handler = async ( params , request , response , context ) => {
// Do some stuff
return {
// Send content and directives to the server
}
} Citizen Server는 handler() 가 초기 요청을 처리하고 4 인수 : 요청의 매개 변수, Node.js request 및 response 개체 및 현재 요청의 컨텍스트를 포함하는 params 개체를 전달합니다.
params 객체의 속성 config | 현재 컨트롤러 동작에 대한 사용자 정의를 포함한 앱 구성 |
route | URL 및 경로 컨트롤러 이름과 같은 요청 된 경로의 세부 사항 |
url | URL에서 파생 된 모든 매개 변수 |
form | 게시물에서 수집 된 데이터 |
payload | 원시 요청 페이로드 |
cookie | 요청과 함께 보낸 쿠키 |
session | 세션 변수 세션이 활성화 된 경우 |
컨트롤러 내에서 이러한 객체에 액세스 할 수있을뿐만 아니라 View Context에 자동으로 포함되므로 View Template 내에서 로컬 변수 (보기 섹션의 자세한 내용)를 참조 할 수 있습니다.
예를 들어, 이전 기사 URL을 기반으로 ...
http://www.cleverna.me/article/My-Clever-Article-Title/id/237/page/2
... 다음 params.url 객체가 컨트롤러로 전달됩니다.
{
article : 'My-Clever-Article-Title' ,
id : '237' ,
page : '2'
} 컨트롤러 이름은 디스크립터를 참조하는 URL 범위의 속성이되어 고유 식별자로 사용하기에 적합합니다. params.route 객체에서 params.route.descriptor 로도 사용할 수 있습니다.
context 인수에는 return 문을 사용하여 체인의 이전 컨트롤러가 생성 한 모든 데이터 또는 지시가 포함되어 있습니다.
컨트롤러 조치의 결과를 반환하려면 시민에게 전달하려는 데이터 및 지침이 포함 된 return 명령문이 포함되어 있습니다.
위의 URL 매개 변수를 사용하여 모델에서 기사 내용을 검색하여 서버로 다시 전달할 수 있습니다.
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
const author = await app . models . article . getAuthor ( {
author : article . author
} )
// Any data you want available to the view should be placed in the local directive
return {
local : {
article : article ,
author : author
}
}
} action URL 매개 변수를 사용하여 대체 작업을 요청할 수 있습니다. 예를 들어, 기사를 편집하기 위해 다른 조치와보기를 원할 수도 있습니다.
// http://www.cleverna.me/article/My-Clever-Article-Title/id/237/page/2/action/edit
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
const author = await app . models . article . getAuthor ( {
author : article . author
} )
// Return the article for view rendering using the local directive
return {
local : {
article : article ,
author : author
}
}
}
export const edit = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
// Use the /views/article/edit.html view for this action
return {
local : {
article : article
} ,
view : 'edit'
}
} return 문 안에 시민에게 전달하려는 데이터를 배치합니다. 귀하의 견해에서 렌더링하려는 모든 데이터는 위와 같이 local 이라는 개체 내에서 시민에게 전달되어야합니다. 서버에 지침을 제공하는 지침을 설정하기 위해 추가 객체를 시민에게 전달할 수 있습니다 (컨트롤러 지침 참조). 컨텍스트에 자신의 객체를 추가하여 컨트롤러에서 컨트롤러로 전달할 수도 있습니다 (컨트롤러 체인 섹션에서 자세히 설명).
모델은 선택적 모듈이며 해당 구조는 완전히 귀하에게 달려 있습니다. 시민은 모델과 직접 대화하지 않습니다. 편의를 위해 app.models 에만 저장합니다. 원하는 경우 컨트롤러로 수동으로 가져올 수도 있습니다.
다음 함수는 app/models/article.js 에 배치 될 때 app.models.article.get() 을 통해 앱에서 액세스 할 수 있습니다.
// app.models.article.get()
export const get = async ( id ) => {
let article = // do some stuff to retrieve the article from the db using the provided ID, then...
return article
} Citizen은 기본적으로보기 렌더링을 위해 템플릿 리터럴을 사용합니다. Consolidate.js를 설치하고 지원되는 템플릿 엔진을 사용할 수 있습니다. 그에 따라 templateEngine 구성 설정을 업데이트하기 만하면됩니다.
article.html 에서는 local 객체 내에 배치 한 변수를 Route Controller의 Return 문으로 전달할 수 있습니다. Citizen은 또한 params Object에서 View Context에 속성을 자동으로 주입하므로 해당 객체에 로컬 변수 (예 : url 범위)에 액세스 할 수 있습니다.
<!-- article.html -->
<!doctype html >
< html >
< body >
< main >
< h1 >
${local.article.title} — Page ${url.page}
</ h1 >
< h2 > ${local.author.name}, ${local.article.published} </ h2 >
< p >
${local.article.summary}
</ p >
< section >
${local.article.text}
</ section >
</ main >
</ body >
</ html > 기본적으로 서버는 이름이 컨트롤러의 이름과 일치하는 뷰를 렌더링합니다. 다른 뷰를 렌더링하려면 반품 명령문에서 view 지시문을 사용하십시오.
모든보기는 /app/views 에 들어갑니다. 컨트롤러에 여러 뷰가있는 경우 해당 컨트롤러의 이름을 따서 명명 된 디렉토리 내에서 구성 할 수 있습니다.
app/
controllers/
routes/
article.js
index.js
views/
article/
article.html // Default article controller view
edit.html
index.html // Default index controller view
Route 컨트롤러에 요청에서 적절한 HTTP Accept 헤더를 설정하여 로컬 변수를 JSON 또는 JSON-P로 반환하도록 지시 할 수 있습니다. 동일한 리소스가 AJAX 요청 및 RESTFUL API에 대한 완전한 HTML보기 및 JSON을 모두 제공 할 수 있습니다.
기사 경로 컨트롤러 handler() 조치가 반환됩니다.
{
"article" : {
"title" : " My Clever Article Title " ,
"summary" : " Am I not terribly clever? " ,
"text" : " This is my article text. "
},
"author" : {
"name" : " John Smith " ,
"email" : " [email protected] "
}
} 컨트롤러의 리턴 명령문에 추가 한 내용이 무엇이든 local 객체가 반환됩니다.
JSONP의 경우 URL에서 callback 사용하십시오.
http://www.cleverna.me/article/My-Clever-Article-Title/callback/foo
보고:
foo ( {
"article" : {
"title" : "My Clever Article Title" ,
"summary" : "Am I not terribly clever?" ,
"text" : "This is my article text."
} ,
"author" : {
"name" : "John Smith" ,
"email" : "[email protected]"
}
} ) ; 주어진 요청에 대한 특정 컨텐츠 유형을 강제하려면 Route 컨트롤러의 response.contentType 설정하십시오.
export const handler = async ( params , request , response ) => {
// Every request will receive a JSON response regardless of the Accept header
response . contentType = 'application/json'
}이벤트 후크 내의 모든 요청에 걸쳐 글로벌 응답 유형을 강제 할 수 있습니다.
도우미는 옵션 유틸리티 모듈이며 해당 구조는 완전히 귀하에게 달려 있습니다. 그들은 당신의 편의를 위해 app.helpers 에 저장되어 있습니다. 원하는 경우 컨트롤러 및 모델로 수동으로 가져올 수도 있습니다.
app/helpers/validate.js 에 배치되면 다음 기능은 app.helpers.validate.email() 을 통해 앱에서 액세스 할 수 있습니다.
// app.helpers.validate.email()
export const email = ( address ) => {
const emailRegex = new RegExp ( / [a-z0-9!##$%&''*+/=?^_`{|}~-]+(?:.[a-z0-9!##$%&''*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])? / i )
return emailRegex . test ( address )
} Citizen은 쉽게 검색 할뿐만 아니라 HMR (Hot Module Replacement)을 지원하기 위해 모든 모듈을 app 범위에 저장합니다. 개발 모드에서 모든 모듈에 대한 변경 사항을 저장하거나 Citizen은 기존 모듈 가져 오기 및 해당 모듈을 실시간으로 다시 홍보합니다.
영향을받는 파일을 나타내는 콘솔 로그가 표시되며 앱이 계속 실행됩니다. 다시 시작할 필요가 없습니다.
시민은 프로세스를 종료하지 않고 오류를 우아하게 처리하기 위해 최선을 다합니다. 다음 컨트롤러 작업은 오류가 발생하지만 서버는 500으로 응답하고 계속 실행됩니다.
export const handler = async ( params ) => {
// app.models.article.foo() doesn't exist, so this action will throw an error
const foo = await app . models . article . foo ( params . url . article )
return {
local : foo
}
}오류를 수동으로 던지고 오류 메시지를 사용자 정의 할 수도 있습니다.
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
// If the article exists, return it
if ( article ) {
return {
local : {
article : article
}
}
// If the article doesn't exist, throw a 404
} else {
// Error messages default to the standard HTTP Status Code response, but you can customize them.
let err = new Error ( 'The requested article does not exist.' )
// The HTTP status code defaults to 500, but you can specify your own
err . statusCode = 404
throw err
}
} params.route.controller 는 요청 된 컨트롤러에서 error 로 업데이트되므로 요청 된 컨트롤러에 대한 앱의 모든 참조가이를 고려해야합니다.
오류는 경로에서 요청한 형식으로 반환됩니다. JSON을 요청하고 경로에 오류가 발생하면 Citizen은 JSON 형식의 오류를 반환합니다.
스캐 폴드 유틸리티에서 생성 된 앱 스켈레톤에는 공통 클라이언트 및 서버 오류에 대한 선택적 오류보기 템플릿이 포함되어 있지만 HTTP 오류 코드에 대한 템플릿을 만들 수 있습니다.
시민의 기본 오류 처리 방법은 capture 되어 우아한 복구를 시도합니다. 오류 후 프로세스를 종료하려면 config.citizen.errors exit 하도록 변경하십시오.
// config file: exit the process after an error
{
"citizen" : {
"errors" : "exit"
}
}응용 프로그램 오류 처리기가 발생한 후 Citizen은 프로세스를 종료합니다.
서버 오류에 대한 사용자 정의 오류보기를 만들려면 /app/views/error 라는 디렉토리를 만들고 HTTP 응답 코드 또는 노드 오류 코드의 이름을 따서 명명 된 템플릿으로 채우십시오.
app/
views/
error/
500.html // Displays any 500-level error
404.html // Displays 404 errors specifically
ENOENT.html // Displays bad file read operations
error.html // Displays any error without its own template
Route Controller Action의 Return 문은 데이터를보기 외에도 대체보기를 렌더링하고 쿠키 및 세션 변수를 설정하고 리디렉션을 시작하고, 콜링 및 렌더링, 캐시 경로 컨트롤러 작업/보기 (또는 전체 요청) 및 추가 처리를 위해 다른 컨트롤러에게 요청을 전달할 수 있습니다.
기본적으로 서버는 이름이 컨트롤러의 이름과 일치하는 뷰를 렌더링합니다. 다른 뷰를 렌더링하려면 반품 명령문에서 view 지시문을 사용하십시오.
// article controller
export const edit = async ( params ) => {
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
return {
local : article ,
// This tells the server to render app/views/article/edit.html
view : 'edit'
}
} 컨트롤러 동작 내에서 cookie 객체를 반환하여 쿠키를 설정합니다.
export const handler = async ( params ) => {
return {
cookie : {
// Cookie shorthand sets a cookie called username using the default cookie properties
username : params . form . username ,
// Sets a cookie called last_active that expires in 20 minutes
last_active : {
value : new Date ( ) . toISOString ( ) ,
expires : 20
}
}
}
}다음은 완전한 쿠키 객체의 기본 설정의 예입니다.
myCookie = {
value : 'myValue' ,
// Valid expiration options are:
// 'now' - deletes an existing cookie
// 'never' - current time plus 30 years, so effectively never
// 'session' - expires at the end of the browser session (default)
// [time in minutes] - expires this many minutes from now
expires : 'session' ,
path : '/' ,
// citizen's cookies are accessible via HTTP/HTTPS only by default. To access a
// cookie via JavaScript, set this to false.
httpOnly : true ,
// Cookies are insecure when set over HTTP and secure when set over HTTPS.
// You can override that behavior globally with the https.secureCookies setting
// in your config or on a case-by-case basis with this setting.
secure : false
} 쿠키가 클라이언트에 설정되면 컨트롤러 내에서 params.cookie 에서 볼 수 있으며보기 내에서 간단히 cookie 사용할 수 있습니다.
<!doctype html >
< html >
< body >
< section >
Welcome, ${cookie.username}.
</ section >
</ body >
</ html > 컨트롤러 내에서 설정 한 쿠키 변수는 params.cookie SCOPE 내에서 즉시 사용할 수 없습니다. Citizen은 컨트롤러로부터 컨텍스트를 받고 고객에게 응답을 먼저 보내야하므로 동일한 요청 중에 액세스 해야하는 경우 변수의 로컬 인스턴스를 사용하십시오.
Citizen이 설정 한 모든 쿠키는 충돌을 피하기 위해 ctzn_ 접두사로 시작합니다. ctzn_ 로 쿠키 이름을 시작하지 마십시오. 아무런 문제가 없어야합니다.
Nginx 또는 Apache와 같은 프록시 뒤에 시민을 사용하는 경우 서버 구성에 HTTP Forwarded 헤더가 있는지 확인하여 시민의 보안 쿠키 처리가 올바르게 작동합니다.
다음은 Nginx에서이를 설정하는 방법의 예입니다.
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:8080;
}
세션이 활성화 된 경우 컨트롤러의 params.session 또는보기 내에서 session 통해 세션 변수에 액세스 할 수 있습니다. 이 로컬 스코프는 세션 ID를 통과하지 않고도 현재 사용자 세션을 참조합니다.
기본적으로 세션에는 id , started , expires 및 timer 네 가지 속성이 있습니다. 세션 ID는 또한 ctzn_session_id 라는 쿠키로 클라이언트에게 전송됩니다.
세션 변수 설정 쿠키 변수 설정과 거의 동일합니다.
return {
session : {
username : 'Danny' ,
nickname : 'Doc'
}
} 쿠키와 마찬가지로 방금 할당 한 세션 변수는 params.session Scope 내에서 동일한 요청 중에 사용할 수 없습니다.이 데이터에 즉시 액세스 해야하는 경우 로컬 인스턴스를 사용하십시오.
세션은 sessions.lifespan 기준으로 만료됩니다. 기본값은 20 분입니다. timer 사용자로부터 각 요청에 따라 재설정됩니다. timer 소진되면 세션이 삭제됩니다. 그 시간 이후의 모든 클라이언트 요청은 새 세션 ID를 생성하고 새 세션 ID 쿠키를 클라이언트에게 보냅니다.
현재 사용자 세션을 강제로 명확하게하고 만료하려면 :
return {
session : {
expires : 'now'
}
} Citizen이 설정 한 모든 세션 변수는 충돌을 피하기 위해 ctzn_ 접두사로 시작합니다. ctzn_ 로 세션 변수 이름을 시작하지 않으면 문제가 없어야합니다.
컨트롤러 작업이 처리 된 후 시작될 서버에 리디렉션 지침을 전달할 수 있습니다.
redirect 객체는 속기 버전에서 URL 문자열을 사용하거나 statusCode , url 및 refresh 의 세 가지 옵션을 취합니다. 상태 코드를 제공하지 않으면 Citizen은 302 (임시 리디렉션)를 사용합니다. refresh 옵션은 리디렉션이 위치 헤더 또는 비표준 새로 고침 헤더를 사용하는지 여부를 결정합니다.
// Initiate a temporary redirect using the Location header
return {
redirect : '/login'
}
// Initiate a permanent redirect using the Refresh header, delaying the redirect by 5 seconds
return {
redirect : {
url : '/new-url' ,
statusCode : 301 ,
refresh : 5
}
} 위치 헤더와 달리 refresh 옵션을 사용하는 경우 Citizen은 리디렉션이 클라이언트면이 발생하므로 클라이언트에 렌더링 된보기를 보냅니다.
위치 헤더를 사용하여 (내 의견으로는) 참조 자원은 리디렉션을 시작한 리소스가 아니라 페이지 이전의 페이지 이전의 리소스가되기 때문에 (내 의견으로는). 이 문제를 해결하기 위해 Citizen은 리디렉션을 시작한 리소스의 URL을 포함하는 ctzn_referer 라는 세션 변수를 저장하며 사용자를 올바르게 리디렉션하는 데 사용할 수 있습니다. 예를 들어, 무분별한 사용자가 보안 페이지에 액세스하려고 시도하고 로그인 양식으로 리디렉션하면 보안 페이지의 주소가 ctzn_referer 에 저장되므로 이전 페이지 대신 보낼 수 있습니다.
세션을 활성화하지 않은 경우 Citizen은 대신 ctzn_referer 라는 쿠키를 만들 수 있습니다.
Nginx 또는 Apache와 같은 프록시 뒤에 시민을 사용하는 경우 서버 구성에 HTTP Forwarded 헤더가 있어야 ctzn_referer 올바르게 작동합니다.
다음은 Nginx에서이를 설정하는 방법의 예입니다.
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:8080;
}
header 지침을 사용하여 HTTP 헤더를 설정할 수 있습니다.
return {
header : {
'Cache-Control' : 'max-age=86400' ,
'Date' : new Date ( ) . toISOString ( )
}
} 또한 Node의 response.setHeader() 사용하여 직접 헤더를 설정할 수 있지만 Citizen의 header 지시문을 사용하면 요청 캐시에서 해당 헤더가 보존되므로 캐시에서 컨트롤러 작업이 시작될 때마다 적용됩니다.
Citizen을 사용하면 Citizen의 구성 요소 버전 인 포함 된 완전한 MVC 패턴을 포함시킬 수 있습니다. 각각 자체 경로 컨트롤러, 모델 및 뷰가 있습니다. 포함을 사용하여 작업을 수행하거나 완전한 렌더링 된보기를 반환 할 수 있습니다. 모든 경로 컨트롤러는 포함 할 수 있습니다.
우리의 기사 패턴 템플릿에 다음 내용이 있다고 가정 해 봅시다. 헤드 섹션에는 동적 메타 데이터가 포함되어 있으며 사용자의 로그인 여부에 따라 헤더의 콘텐츠가 변경됩니다.
<!doctype html >
< html >
< head >
< title > ${local.metaData.title} </ title >
< meta name =" description " content =" ${local.metaData.description} " >
< meta name =" keywords " content =" ${local.metaData.keywords} " >
< link rel =" stylesheet " type =" text/css " href =" site.css " >
</ head >
< body >
< header >
${ cookie.username ? ' < p > Welcome, ' + cookie.username + ' </ p > ' : ' < a href =" /login " > Login </ a > ' }
</ header >
< main >
< h1 > ${local.article.title} — Page ${url.page} </ h1 >
< p > ${local.article.summary} </ p >
< section > ${local.article.text} </ section >
</ main >
</ body >
</ html >헤드 섹션과 헤더에 포함 된 것은 아마도 모든 곳에서 해당 코드를 사용할 수 있지만 간단한 부분보다는 시민을 포함시킬 수 있기 때문에 포함하는 것이 합리적 일 것입니다. 헤드 섹션은 메타 데이터를 채우기 위해 자체 모델을 사용할 수 있으며, 인증 된 사용자의 헤더는 다르기 때문에 뷰에서 해당 논리를 꺼내 헤더 컨트롤러에 넣으십시오. 나는 밑줄로 부분을 시작하는 협약을 따르고 싶지만 그것은 당신에게 달려 있습니다.
app/
controllers/
routes/
_head.js
_header.js
article.js
models/
_head.js
article.js
views/
_head.html
_header/
_header.html
_header-authenticated.html // A different header for logged in users
article.html
기사 컨트롤러가 해고되면 Citizen에게 IT 요구 사항을 포함해야합니다. 우리는 include 지시문으로 다음을 수행합니다.
// article controller
export const handler = async ( params ) => {
// Get the article
const article = await app . models . article . get ( {
article : params . url . article ,
page : params . url . page
} )
return {
local : {
article : article
} ,
include : {
// Include shorthand is a string containing the pathname to the desired route controller
_head : '/_head/action/article' ,
// Long-form include notation can explicitly define a route controller, action, and view
_header : {
controller : '_header' ,
// If the username cookie exists, use the authenticated action. If not, use the default action.
action : params . cookie . username ? 'authenticated' : 'handler'
}
}
}
} 시민은 패턴이 공개 행동이있는 컨트롤러를 포함하여 일반 패턴과 동일한 요구 사항을 갖습니다. 위의 include 지시문은 Citizen에게 _head 및 _header 컨트롤러를 호출하고 article 컨트롤러 (Params, Request, Response, Context)에 전달 된 것과 동일한 인수를 전달하고 각각의보기를 렌더링하며 뷰 컨텍스트에 결과 뷰를 추가하도록 지시합니다.
헤드 섹션 컨트롤러는 다음과 같습니다.
// _head controller
export const article = async ( params ) => {
let metaData = await app . models . _head ( { article : params . url . article } )
return {
local : {
metaData : metaData
}
}
}헤드 섹션보기 :
< head >
< title > ${local.metaData.title} </ title >
< meta name =" description " content =" ${local.metaData.description} " >
< meta name =" keywords " content =" ${local.metaData.keywords} " >
< link rel =" stylesheet " type =" text/css " href =" site.css " >
</ head >헤더 컨트롤러는 다음과 같습니다.
// _header controller
// No need for a return statement, and no need to specify the view
// because handler() renders the default view.
//
// Every route controller needs at least one action, even if it's empty.
export const handler = ( ) => { }
export const authenticated = ( ) => {
return {
view : '_header-authenticated'
}
}헤더 뷰 :
<!-- /views/_header/_header.html -->
< header >
< a href =" /login " > Login </ a >
</ header > <!-- /views/_header/_header-authenticated.html -->
< header >
< p > Welcome, ${cookie.username} </ p >
</ header > 렌더링 된 포함은 include 범위에 저장됩니다.
<!-- /views/article.html -->
<!doctype html >
< html >
${include._head}
< body >
${include._header}
< main >
< h1 > ${local.title} — Page ${url.page} </ h1 >
< p > ${local.summary} </ p >
< section > ${local.text} </ section >
</ main >
</ body >
</ html >Citizen 포함은 자체 포함되어 있으며 완전히 렌더링 된 전망으로 전화 컨트롤러에 전달됩니다. 호출 컨트롤러와 동일한 데이터 (URL 매개 변수, 양식 입력, 요청 컨텍스트 등)를 수신하지만 포함 내부에서 생성 된 데이터는 발신자에게 전달되지 않습니다.
포함 된 것으로 사용되는 패턴은 다른 경로 컨트롤러와 마찬가지로 HTTP를 통해 액세스 할 수 있습니다. _header 컨트롤러와 같은 _header 컨트롤러를 요청하고 응답으로 HTML 또는 JSON 덩어리를받을 수 있습니다.
http://cleverna.me/_header
이는 첫 번째 요청 서버 측을 처리 한 다음 클라이언트 측 라이브러리로 콘텐츠를 업데이트하는 데 좋습니다.
시민은 풍부한 기능을 제공하지만 특정 상황에서는 한계가 있으며 과잉이 될 수 있습니다.
Citizen을 사용하면 next 지침을 사용하여 단일 요청에서 여러 경로 컨트롤러를 시리즈로 함께 체인 할 수 있습니다. 요청 된 컨트롤러는 데이터를 전달하고 후속 컨트롤러로보기를 렌더링하여 자체 데이터를 추가하고 자체보기를 렌더링합니다.
원하는만큼 단일 요청으로 많은 경로 컨트롤러를 함께 문자 할 수 있습니다. 각 경로 컨트롤러에는 데이터 및 뷰 출력이 params.route.chain 객체에 저장됩니다.
// The index controller accepts the initial request and hands off execution to the article controller
export const handler = async ( params ) => {
let user = await app . models . user . getUser ( { userID : params . url . userID } )
return {
local : {
user : user
} ,
// Shorthand for next is a string containing the pathname to the route controller.
// URL paramaters in this route will be parsed and handed to the next controller.
next : '/article/My-Article/id/5'
// Or, you can be explicit, but without parameters
next : {
// Pass this request to app/controllers/routes/article.js
controller : 'article' ,
// Specifying the action is optional. The next controller will use its default action, handler(), unless you specify a different action here.
action : 'handler' ,
// Specifying the view is optional. The next controller will use its default view unless you tell it to use a different one.
view : 'article'
}
// You can also pass custom directives and data.
doSomething: true
}
}체인의 각 컨트롤러는 이전 컨트롤러의 컨텍스트 및 뷰에 액세스 할 수 있습니다. 체인의 마지막 컨트롤러는 최종 렌더링 뷰를 제공합니다. A layout controller with all your site's global elements is a common use for this.
// The article controller does its thing, then hands off execution to the _layout controller
export const handler = async ( params , request , response , context ) => {
let article = await getArticle ( { id : params . url . id } )
// The context from the previous controller is available to you in the current controller.
if ( context . doSomething ) { // Or, params.route.chain.index.context
await doSomething ( )
}
return {
local : {
article : article
} ,
next : '/_layout'
}
} The rendered view of each controller in the chain is stored in the route.chain object:
<!-- index.html, which is stored in route.chain.index.output -->
< h1 > Welcome, ${local.user.username}! </ h1 >
<!-- article.html, which is stored in route.chain.article.output -->
< h1 > ${local.article.title} </ h1 >
< p > ${local.article.summary} </ p >
< section > ${local.article.text} </ section >The layout controller handles the includes and renders its own view. Because it's the last controller in the chain, this rendered view is what will be sent to the client.
// _layout controller
export const handler = async ( params ) => {
return {
include : {
_head : '/_head' ,
_header : {
controller : '_header' ,
action : params . cookie . username ? 'authenticated' : 'handler'
} ,
_footer : '/_footer
}
}
} <!-- _layout.html -->
<!doctype html >
< html >
${include._head}
< body >
${include._header}
< main >
<!-- You can render each controller's view explicitly -->
${route.chain.index.output}
${route.chain.article.output}
<!-- Or, you can loop over the route.chain object to output the view from each controller in the chain -->
${Object.keys(route.chain).map( controller = > { return route.chain[controller].output }).join('')}
</ main >
${include._footer}
</ body >
</ html > You can skip rendering a controller's view in the chain by setting the view directive to false:
// This controller action won't render a view
export const handler = async ( ) => {
return {
view : false ,
next : '/_layout'
}
} To bypass next in a request, add /direct/true to the URL.
http://cleverna.me/index/direct/true
The requested route controller's next directive will be ignored and its view will be returned to the client directly.
As mentioned in the config section at the beginning of this document, you can specify a default layout controller in your config so you don't have to insert it at the end of every controller chain:
{
"citizen" : {
"layout" : {
"controller" : " _layout " ,
"view" : " _layout "
}
}
} If you use this method, there's no need to use next for the layout. The last controller in the chain will always hand the request to the layout controller for final rendering.
citizen provides several ways for you to improve your app's performance, most of which come at the cost of system resources (memory or CPU).
In many cases, a requested URL or route controller action will generate the same view every time based on the same input parameters, so it doesn't make sense to run the controller chain and render the view from scratch for each request. citizen provides flexible caching capabilities to speed up your server side rendering via the cache directive.
If a given request (URL) will result in the exact same rendered view with every request, you can cache that request with the request property. This is the fastest cache option because it pulls a fully rendered view from memory and skips all controller processing.
Let's say you chain the index, article, and layout controllers like we did above. If you put the following cache directive in your index controller, the requested URL's response will be cached and subsequent requests will skip the index, article, and layout controllers entirely.
return {
next : '/article' ,
cache : {
request : true
}
}For the request cache directive to work, it must be placed in the first controller in the chain; in other words, the original requested route controller (index in this case). It will be ignored in any subsequent controllers.
The URL serves as the cache key, so each of the following URLs would generate its own cache item:
http://cleverna.me/article
http://cleverna.me/article/My-Article
http://cleverna.me/article/My-Article/page/2
The example above is shorthand for default cache settings. The cache.request directive can also be an object with options:
// Cache the requested route with some additional options
return {
cache : {
request : {
// Optional. This setting lets the server respond with a 304 Not Modified
// status if the cache content hasn't been updated since the client last
// accessed the route. Defaults to the current time if not specified.
lastModified : new Date ( ) . toISOString ( ) ,
// Optional. List of valid URL parameters that protects against accidental
// caching of malformed URLs.
urlParams : [ 'article' , 'page' ] ,
// Optional. Life of cached item in minutes. Default is 15 minutes.
// For no expiration, set to 'application'.
lifespan : 15 ,
// Optional. Reset the cached item's expiration timer whenever the item is
// accessed, keeping it in the cache until traffic subsides. Default is true.
resetOnAccess : true
}
}
} If a given route chain will vary across requests, you can still cache individual controller actions to speed up rendering using the action property.
// Cache this controller action using the default settings
return {
cache : {
action : true
}
}
// Cache this controller with additional options
return {
cache : {
action : {
// These options function the same as request caching (see above)
urlParams : [ 'article' , 'page' ] ,
lifespan : 15 ,
resetOnAccess : true
}
}
}When you cache controller actions, their context is also cached. Setting a cookie or session variable in a cached controller action means all future requests for that action will set the same cookie or session variable—probably not something you want to do with user data.
lastModified This setting lets the server respond with a faster 304 Not Modified response if the content of the request cache hasn't changed since the client last accessed it. By default, it's set to the time at which the request was cached, but you can specify a custom date in ISO format that reflects the last modification to the request's content.
return {
next : '/_layout' ,
cache : {
request : {
// Use toISOString() to format your date appropriately
lastModified : myDate . toISOString ( ) // 2015-03-05T08:59:51.491Z
}
}
} urlParams The urlParams property helps protect against invalid cache items (or worse: an attack meant to flood your server's resources by overloading the cache).
return {
next : '/_layout' ,
cache : {
request : {
urlParams : [ 'article' , 'page' ]
}
}
}If we used the example above in our article controller, the following URLs would be cached because the "article" and "page" URL parameters are permitted:
http://cleverna.me/article
http://cleverna.me/article/My-Article-Title
http://cleverna.me/article/My-Article-Title/page/2
The following URLs wouldn't be cached, which is a good thing because it wouldn't take long for an attacker's script to loop over a URL and flood the cache:
http://cleverna.me/article/My-Article-Title/dosattack/1
http://cleverna.me/article/My-Article-Title/dosattack/2
http://cleverna.me/article/My-Article-Title/page/2/dosattack/3
The server logs a warning when invalid URL parameters are present, but continues processing without caching the result.
lifespanThis setting determines how long the request or controller action should remain in the cache, in minutes.
return {
cache : {
request : {
// This cached request will expire in 10 minutes
lifespan : 10
}
}
} resetOnAccess Used with the lifespan setting, resetOnAccess will reset the timer of the route or controller cache whenever it's accessed, keeping it in the cache until traffic subsides. Defaults to true .
return {
cache : {
request : {
// This cached request will expire in 10 minutes, but if a request accesses it
// before then, the cache timer will be reset to 10 minutes from now
lifespan : 10 ,
resetOnAccess : true
}
}
} In most cases, you'll probably want to choose between caching an entire request (URL) or caching individual controller actions, but not both.
When caching an include controller action, the route pathname pointing to that include is used as the cache key. If you use logic to render different views using the same controller action, the first rendered view will be cached. You can pass an additional URL parameter in such cases to get past this limitation and create a unique cache item for different include views.
export const handler = async ( context ) => {
return : {
// Two different versions of the _header include will be cached becaues the URLs are unique
include : context . authenticated ? '/_header/authenticated/true' : '/_header'
}
} citizen's cache is a RAM cache stored in the V8 heap, so be careful with your caching strategy. Use the lifespan and resetOnAccess options so URLs that receive a lot of traffic stay in the cache, while less popular URLs naturally fall out of the cache over time.
By caching static assets in memory, you speed up file serving considerably. To enable static asset caching for your app's public (web) directory, set cache.static.enabled to true in your config:
{
"citizen" : {
"cache" : {
"static" : {
"enabled" : true
}
}
}
}citizen handles response headers automatically (ETags, 304 status codes, etc.) using each file's last modified date. Note that if a file changes after it's been cached, you'll need to clear the file cache using cache.clear() or restart the app.
To clear a file from the cache in a running app:
app . cache . clear ( { file : '/absolute/path/to/file.jpg' } )With static caching enabled, all static files citizen serves will be cached in the V8 heap, so keep an eye on your app's memory usage to make sure you're not using too many resources.
citizen automatically sets ETag headers for cached requests and static assets. You don't need to do anything to make them work. The Cache-Control header is entirely manual, however.
To set the Cache-Control header for static assets, use the cache.control setting in your config:
{
"citizen" : {
"cache" : {
"static" : true ,
"control" : {
"/css/global.css" : " max-age=86400 " ,
"/css/index.css" : " max-age=86400 " ,
"/js/global.js" : " max-age=86400 " ,
"/js/index.js" : " max-age=86400 " ,
"/images/logo.png" : " max-age=31536000 "
}
}
}
} The key name is the pathname that points to the static asset in your web directory. If your app's URL path is /my/app , then this value should be something like /my/app/styles.css . The value is the Cache-Control header value you want to assign to that asset.
You can use strings that match the exact pathname like above, or you can also use wildcards. Mixing the two is fine:
{
"citizen" : {
"cache" : {
"static" : true ,
"control" : {
"/css/*" : " max-age=86400 " ,
"/js/*" : " max-age=86400 " ,
"/images/logo.png" : " max-age=31536000 "
}
}
}
}Here's a great tutorial on client-side caching to help explain ETag and Cache-Control headers.
Both dynamic routes and static assets can be compressed before sending them to the browser. To enable compression for clients that support it:
{
"citizen" : {
"compression" : {
"enabled" : true
}
}
}Proxies, firewalls, and other network circumstances can strip the request header that tells the server to provide compressed assets. You can force gzip or deflate for all clients like this:
{
"citizen" : {
"compression" : {
"enabled" : true ,
"force" : " gzip "
}
}
}If you have request caching enabled, both the original (identity) and compressed (gzip and deflate) versions of the request will be cached, so your cache's memory utilization will increase.
citizen includes basic request payload parsing. When a user submits a form, the parsed form data is available in your controller via params.form . If you want to use a third-party package to parse the form data yourself, you can disable form parsing in the config and access the raw payload via request.payload .
// login controller
export const handler = ( params ) => {
// Set some defaults for the login view
params . form . username = ''
params . form . password = ''
params . form . remember = false
}
// Using a separate action in your controller for form submissions is probably a good idea
export const submit = async ( params ) => {
let authenticate = await app . models . user . authenticate ( {
username : params . form . username ,
password : params . form . password
} ) ,
cookies = { }
if ( authenticate . success ) {
if ( params . form . remember ) {
cookies . username : authenticate . username
}
return {
cookies : cookies ,
redirect : '/'
}
} else {
return {
local : {
message : 'Login failed.'
}
}
}
}If it's a multipart form containing a file, the form object passed to your controller will look something like this:
{
field1 : 'bar' ,
field2 : 'buzz' ,
fileField1 : {
filename : 'file.png' ,
contentType : 'image/png' ,
binary : < binary data >
}
} File contents are presented in binary format, so you'll need to use Buffer.from(fileField1.binary, 'binary') to create the actual file for storage.
You can pass global form settings via citizen.form in the config or at the controller action level via controller config (see below).
Use the maxPayloadSize config to limit form uploads. The following config sets the maxFieldsSize to 512k:
{
"citizen" : {
"forms" : {
"maxPayloadSize" : 500000 // 0.5MB
}
}
} The maxPayloadSize option includes text inputs and files in a multipart form in its calculations. citizen throws an error if form data exceeds this amount.
Certain events will occur throughout the life of your citizen application, or within each request. You can act on these events, execute functions, set directives, and pass the results to the next event or controller via the context argument. For example, you might set a cookie at the beginning of every new session, or check for cookies at the beginning of every request and redirect the user to a login page if they're not authenticated.
To take advantage of these events, include a directory called "hooks" in your app with any or all of following modules and exports:
app/
controllers/
hooks/
application.js // exports start() and error()
request.js // exports start() and end()
response.js // exports start() and end()
session.js // exports start() and end()
request.start() , request.end() , and response.start() are called before your controller is fired, so the output from those events is passed from each one to the next, and ultimately to your controller via the context argument. Exactly what actions they perform and what they output—content, citizen directives, custom directives—is up to you.
All files and exports are optional. citizen parses them at startup and only calls them if they exist. For example, you could have only a request.js module that exports start() .
Here's an example of a request module that checks for a username cookie at the beginning of every request and redirects the user to the login page if it doesn't exist. We also avoid a redirect loop by making sure the requested controller isn't the login controller:
// app/controllers/hooks/request.js
export const start = ( params ) => {
if ( ! params . cookie . username && params . route . controller !== 'login' ) {
return {
redirect = '/login'
}
}
} session.end is slightly different in terms of the arguments it receives, which consists only of a copy of the expired session (no longer active):
// app/controllers/hooks/session.js
export const end = ( expiredSession ) => {
// do something whenever a session ends
} By default, all controllers respond to requests from the host only. citizen supports cross-domain HTTP requests via access control headers.
To enable cross-domain access for individual controller actions, add a cors object with the necessary headers to your controller's exports:
export const config = {
// Each controller action can have its own CORS headers
handler : {
cors : {
'Access-Control-Allow-Origin' : 'http://www.foreignhost.com' ,
'Access-Control-Expose-Headers' : 'X-My-Custom-Header, X-Another-Custom-Header' ,
'Access-Control-Max-Age' : 600 ,
'Access-Control-Allow-Credentials' : 'true' ,
'Access-Control-Allow-Methods' : 'OPTIONS, PUT' ,
'Access-Control-Allow-Headers' : 'Content-Type' ,
'Vary' : 'Origin'
}
}
} Why not just use the HTTP Headers directive or set them manually with response.setHeader() ? When citizen receives a request from an origin other than the host, it checks for the cors export in your controller to provide a preflight response without you having to write your own logic within the controller action. You can of course check request.method and write logic to handle this manually if you prefer.
For more details on CORS, check out the W3C spec and the Mozilla Developer Network.
If you use citizen behind a proxy, such as NGINX or Apache, make sure you have a Forwarded header in your server configuration so citizen handles CORS requests correctly. Different protocols (HTTPS on your load balancer and HTTP in your citizen app) will cause CORS requests to fail without these headers.
Here's an example of how you might set this up in NGINX:
location / {
proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme;";
proxy_pass http://127.0.0.1:3000;
}
citizen has a built-in application cache where you can store basically anything: strings, objects, buffers, static files, etc.
You can store any object in citizen's cache. The benefits of using cache over storing content in your own global app variables are built-in cache expiration and extension, as well as wrappers for reading, parsing, and storing file content.
citizen's default cache time is 15 minutes, which you can change in the config (see Configuration). Cached item lifespans are extended whenever they're accessed unless you pass resetOnAccess: false or change that setting in the config.
// Cache a string in the default app scope for 15 minutes (default). Keys
// must be unique within a given scope.
app . cache . set ( {
key : 'welcome-message' ,
value : 'Welcome to my site.'
} )
// Cache a string under a custom scope, which is used for retrieving or clearing
// multiple cache items at once. Keys must be unique within a given scope.
// Reserved scope names are "app", "routes", and "files".
app . cache . set ( {
key : 'welcome-message' ,
scope : 'site-messages' ,
value : 'Welcome to our site.'
} )
// Cache a string for the life of the application.
app . cache . set ( {
key : 'welcome-message' ,
value : 'Welcome to my site.' ,
lifespan : 'application'
} )
// Cache a file buffer using the file path as the key. This is a wrapper for
// fs.readFile and fs.readFileSync paired with citizen's cache function.
// Optionally, tell citizen to perform a synchronous file read operation and
// use an encoding different from the default (UTF-8).
app . cache . set ( {
file : '/path/to/articles.txt' ,
synchronous : true ,
encoding : 'CP-1252'
} )
// Cache a file with a custom key. Optionally, parse the JSON and store the
// parsed object in the cache instead of the raw buffer. Expire the cache
// after 10 minutes, regardless of whether the cache is accessed or not.
app . cache . set ( {
file : '/path/to/articles.json' ,
key : 'articles' ,
parseJSON : true ,
lifespan : 10 ,
resetOnAccess : false
} ) app , routes , and files are reserved scope names, so you can't use them for your own custom scopes.
This is a way to check for the existence of a given key or scope in the cache without resetting the cache timer on that item. Returns false if a match isn't found.
// Check for the existence of the specified key
let keyExists = app . cache . exists ( { key : 'welcome-message' } ) // keyExists is true
let keyExists = app . cache . exists ( { file : '/path/to/articles.txt' } ) // keyExists is true
let keyExists = app . cache . exists ( { file : 'articles' } ) // keyExists is true
let keyExists = app . cache . exists ( { key : 'foo' } ) // keyExists is false
// Check the specified scope for the specified key
let keyExists = app . cache . exists ( {
scope : 'site-messages' ,
key : 'welcome-message'
} )
// keyExists is true
// Check if the specified scope exists and contains items
let scopeExists = app . cache . exists ( {
scope : 'site-messages'
} )
// scopeExists is true
// Check if the route cache has any instances of the specified route
let controllerExists = app . cache . exists ( {
route : '/article'
} ) Retrieve an individual key or an entire scope. Returns false if the requested item doesn't exist. If resetOnAccess was true when the item was cached, using retrieve() will reset the cache clock and extend the life of the cached item. If a scope is retrieved, all items in that scope will have their cache timers reset.
Optionally, you can override the resetOnAccess attribute when retrieving a cache item by specifying it inline.
// Retrieve the specified key from the default (app) scope
let welcomeMessage = app . cache . get ( {
key : 'welcome-message'
} )
// Retrieve the specified key from the specified scope and reset its cache timer
// even if resetOnAccess was initially set to false when it was stored
let welcomeMessage = app . cache . get ( {
scope : 'site-messages' ,
key : 'welcome-message' ,
resetOnAccess : true
} )
// Retrieve all keys from the specified scope
let siteMessages = app . cache . get ( {
scope : 'site-messages'
} )
// Retrieve a cached file
let articles = app . cache . get ( {
file : '/path/to/articles.txt'
} )
// Retrieve a cached file with its custom key
let articles = app . cache . get ( {
file : 'articles'
} )Clear a cache object using a key or a scope.
// Store some cache items
app . cache . set ( {
key : 'welcome-message' ,
scope : 'site-messages' ,
value : 'Welcome to our site.'
} )
app . cache . set ( {
key : 'goodbye-message' ,
scope : 'site-messages' ,
value : 'Thanks for visiting!'
} )
app . cache . set ( {
file : '/path/to/articles.txt' ,
synchronous : true
} )
// Clear the welcome message from its custom scope cache
app . cache . clear ( { scope : 'site-messages' , key : 'welcome-message' } )
// Clear all messages from the cache using their custom scope
app . cache . clear ( { scope : 'site-messages' } )
// Clear the articles cache from the file scope
app . cache . clear ( { file : '/path/to/articles.txt' } ) cache.clear() can also be used to delete cached requests and controller actions.
app . cache . clear ( {
route : '/article/My-Article/page/2'
} )
// Clear the entire route scope
app . cache . clear ( { scope : 'routes' } )
// Clear the entire file scope
app . cache . clear ( { scope : 'files' } )
// Clear the entire cache
app . cache . clear ( ) citizen's log() function is exposed for use in your app via app.log() .
Makes it easy to log comments to either the console or a file (or both) in a way that's dependent on the mode of the framework.
When citizen is in production mode, log() does nothing by default. In development mode, log() will log whatever you pass to it. This means you can place it throughout your application's code and it will only write to the log in development mode. You can override this behavior globally with the log settings in your config file or inline with the console or file options when calling log() .
app . log ( {
// Optional. Valid settings are "status" (default) or "error".
type : 'status' ,
// Optional string. Applies a label to your log item.
label : 'Log output' ,
// The content of your log. If it's anything other than a string or
// number, log() will run util.inspect on it and dump the contents.
contents : someObject ,
// Optional. Enables console logs.
console : true ,
// Optional. Enables file logging.
file : false ,
// Optional. File name you'd like to write your log to.
file : 'my-log-file.log' ,
// Optional. Disables the timestamp that normally appears in front of the log
timestamp : false
} ) Log files appear in the directory you specify in config.citizen.directories.logs .
Warning: development mode is inherently insecure. Don't use it in a production environment.
If you set "mode": "development" in your config file, citizen dumps all major operations to the console.
You can also dump the request context to the view by setting development.debug.view in your config file to true , or use the ctzn_debug URL parameter on a per-request basis:
// config file: always dumps debug output in the view
{
"citizen" : {
"development" : {
"debug" : {
"view" : true
}
}
}
} By default, citizen dumps the pattern's complete context. You can specify the exact object to debug with the ctzn_inspect URL parameter:
// Dumps the server params object
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params
// Dumps the user's session scope
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params.session
The debug output traverses objects 4 levels deep by default. To display deeper output, use the development.debug.depth setting in your config file or append ctzn_debugDepth to the URL. Debug rendering will take longer the deeper you go.
// config file: debug 4 levels deep
{
"citizen" : {
"development" : {
"debug" : {
"depth" : 6
}
}
}
}
// URL
// http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_debugDepth/4 In development mode, you must specify the ctzn_debug URL parameter to display debug output. Debug output is disabled in production mode.
The util directory within the citizen package has some helpful utilities.
Creates a complete skeleton of a citizen app with a functional index pattern and error templates.
$ node node_modules/citizen/util/scaffold skeletonResulting file structure:
app/
config/
citizen.json
controllers/
hooks/
application.js
request.js
response.js
session.js
routes/
index.js
models/
index.js
views/
error/
404.html
500.html
ENOENT.html
error.html
index.html
start.js
web/
Run node node_modules/citizen/util/scaffold skeleton -h for options.
Creates a complete citizen MVC pattern. The pattern command takes a pattern name and options:
$ node node_modules/citizen/util/scaffold pattern [options] [pattern] For example, node scaffold pattern article will create the following pattern:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html
Use node node_modules/citizen/util/scaffold pattern -h to see all available options for customizing your patterns.
(The MIT License)
Copyright (c) 2014-2024 Jay Sylvester
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
By default, citizen dumps the pattern's complete context. You can specify the exact object to debug with the ctzn_inspect URL parameter:
// Dumps the server params object
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params
// Dumps the user's session scope
http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_inspect/params.session
The debug output traverses objects 4 levels deep by default. To display deeper output, use the development.debug.depth setting in your config file or append ctzn_debugDepth to the URL. Debug rendering will take longer the deeper you go.
// config file: debug 4 levels deep
{
"citizen" : {
"development" : {
"debug" : {
"depth" : 6
}
}
}
}
// URL
// http://www.cleverna.me/article/id/237/page/2/ctzn_debug/true/ctzn_debugDepth/4 In development mode, you must specify the ctzn_debug URL parameter to display debug output. Debug output is disabled in production mode.
The util directory within the citizen package has some helpful utilities.
Creates a complete skeleton of a citizen app with a functional index pattern and error templates.
$ node node_modules/citizen/util/scaffold skeletonResulting file structure:
app/
config/
citizen.json
controllers/
hooks/
application.js
request.js
response.js
session.js
routes/
index.js
models/
index.js
views/
error/
404.html
500.html
ENOENT.html
error.html
index.html
start.js
web/
Run node node_modules/citizen/util/scaffold skeleton -h for options.
Creates a complete citizen MVC pattern. The pattern command takes a pattern name and options:
$ node node_modules/citizen/util/scaffold pattern [options] [pattern] For example, node scaffold pattern article will create the following pattern:
app/
controllers/
routes/
article.js
models/
article.js
views/
article/
article.html
Use node node_modules/citizen/util/scaffold pattern -h to see all available options for customizing your patterns.
(The MIT License)
Copyright (c) 2014-2024 Jay Sylvester
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.