1 | About [<img src="https://secure.travis-ci.org/1602/compound.png" />](http://travis-ci.org/#!/1602/compound) [![Code Climate](https://codeclimate.com/repos/550d7c5c69568077bd01488f/badges/e7b56f30b1a89120a1f5/gpa.svg)](https://codeclimate.com/repos/550d7c5c69568077bd01488f/feed)
|
2 | =====
|
3 |
|
4 | <img src="https://raw.github.com/1602/compound/master/templates/public/images/compound.png" />
|
5 |
|
6 | CompoundJS - MVC framework for NodeJS™. It allows you to build web application in minutes.
|
7 |
|
8 | Compound modules now available at https://github.com/compoundjs
|
9 |
|
10 | Full documentation is available at http://compoundjs.com/ and using man(1).
|
11 |
|
12 | Installation
|
13 | ============
|
14 |
|
15 | Option 1: npm
|
16 |
|
17 | ```sh
|
18 | sudo npm install compound -g
|
19 | ```
|
20 |
|
21 | Option 2: GitHub
|
22 |
|
23 | ```sh
|
24 | sudo npm install 1602/compound
|
25 | ```
|
26 |
|
27 | Usage
|
28 | =====
|
29 |
|
30 | ```sh
|
31 | # initialize app
|
32 | compound init blog && cd blog
|
33 | npm install
|
34 |
|
35 | # generate scaffold
|
36 | compound generate crud post title content published:boolean
|
37 |
|
38 | # run server on port 3000
|
39 | compound s 3000
|
40 |
|
41 | # visit app
|
42 | open http://localhost:3000/posts
|
43 | ```
|
44 |
|
45 | Short functionality review
|
46 | ==========================
|
47 |
|
48 | CLI tool
|
49 | --------
|
50 |
|
51 | ```
|
52 | $ compound help
|
53 | Usage: compound command [argument(s)]
|
54 |
|
55 | Commands:
|
56 | h, help Display usage information
|
57 | i, init Initialize compound app
|
58 | g, generate [smth] Generate something awesome
|
59 | r, routes [filter] Display application routes
|
60 | c, console Debug console
|
61 | s, server [port] Run compound server
|
62 | install [module] Installs a compound module and patches the autoload file
|
63 | ```
|
64 |
|
65 | #### compound init [appname][ option(s)]
|
66 |
|
67 | ```
|
68 | options:
|
69 | --coffee # Default: no coffee by default
|
70 | --tpl jade|ejs # Default: ejs
|
71 | --css sass|less|stylus # Default: stylus
|
72 | --db redis|mongodb|nano|mysql|sqlite3|postgres
|
73 | # Default: memory
|
74 | ```
|
75 |
|
76 | #### compound generate smth
|
77 |
|
78 | smth = generator name (controller, model, scaffold, ...can be extended via plugins)
|
79 |
|
80 | more information about generators available here:
|
81 | http://compoundjs.github.com/generators
|
82 |
|
83 | #### compound server 8000
|
84 |
|
85 | equals to `PORT=8000 node server` - run server on port `8000`
|
86 |
|
87 | #### compound console
|
88 |
|
89 | run debugging console (see details below)
|
90 |
|
91 | #### compound routes
|
92 |
|
93 | print routes map (see details below)
|
94 |
|
95 |
|
96 | Directory structure
|
97 | -------------------
|
98 |
|
99 | On initialization directories tree generated, like that:
|
100 |
|
101 | ```
|
102 | .
|
103 | |-- app
|
104 | | |-- assets
|
105 | | | |-- coffeescripts
|
106 | | | | `-- application.coffee
|
107 | | | `-- stylesheets
|
108 | | | `-- application.styl
|
109 | | |-- controllers
|
110 | | | |-- admin
|
111 | | | | |-- categories_controller.js
|
112 | | | | |-- posts_controller.js
|
113 | | | | `-- tags_controller.js
|
114 | | | |-- comments_controller.js
|
115 | | | `-- posts_controller.js
|
116 | | |-- models
|
117 | | | |-- category.js
|
118 | | | |-- post.js
|
119 | | | `-- tag.js
|
120 | | |-- tools
|
121 | | | `-- database.js
|
122 | | |-- views
|
123 | | | |-- admin
|
124 | | | | `-- posts
|
125 | | | | |-- edit.ejs
|
126 | | | | |-- index.ejs
|
127 | | | | |-- new.ejs
|
128 | | | |-- layouts
|
129 | | | | `-- application_layout.ejs
|
130 | | | |-- partials
|
131 | | | `-- posts
|
132 | | | |-- index.ejs
|
133 | | | `-- show.ejs
|
134 | | `-- helpers
|
135 | | |-- admin
|
136 | | | |-- posts_helper.js
|
137 | | | `-- tags_helper.js
|
138 | | `-- posts_helper.js
|
139 | `-- config
|
140 | |-- database.json
|
141 | |-- routes.js
|
142 | |-- tls.cert
|
143 | `-- tls.key
|
144 | ```
|
145 |
|
146 | HTTPS Support
|
147 | -------------
|
148 |
|
149 | Just place your key and cert into config directory, compound will use it.
|
150 | Default names for keys are `tls.key` and `tls.cert`, but you can store in in another place, in that case just pass filenames to createServer function:
|
151 | `server.js`
|
152 |
|
153 | ```js
|
154 | require('compound').createServer({
|
155 | key: fs.readFileSync('/tmp/tls.key').toString(),
|
156 | cert: fs.readFileSync('/tmp/tls.cert').toString()
|
157 | });
|
158 | ```
|
159 |
|
160 | Few helpful commands:
|
161 |
|
162 | ```sh
|
163 | # generate private key
|
164 | openssl genrsa -out /tmp/tls.key
|
165 | # generate cert
|
166 | openssl req -new -x509 -key /tmp/tls.key -out /tmp/tls.cert -days 1095 -batch
|
167 | ```
|
168 |
|
169 | Routing
|
170 | -------
|
171 |
|
172 | Now we do not have to tediously describe REST routes for each resource, enough to write in `config/routes.js` code like this:
|
173 |
|
174 | ```js
|
175 | exports.routes = function (map) {
|
176 | map.resources('posts', function (post) {
|
177 | post.resources('comments');
|
178 | });
|
179 | };
|
180 | ```
|
181 |
|
182 | instead of:
|
183 |
|
184 | ```js
|
185 | var ctl = require('./lib/posts_controller.js');
|
186 | app.get('/posts/new.:format?', ctl.new);
|
187 | app.get('/posts.:format?', ctl.index);
|
188 | app.post('/posts.:format?', ctl.create);
|
189 | app.get('/posts/:id.:format?', ctl.show);
|
190 | app.put('/posts/:id.:format?', ctl.update);
|
191 | app.delete('/posts/:id.:format?', ctl.destroy);
|
192 | app.get('/posts/:id/edit.:format?', ctl.edit);
|
193 |
|
194 | var com_ctl = require('./lib/comments_controller.js');
|
195 | app.get('/posts/:post_id/comments/new.:format?', com_ctl.new);
|
196 | app.get('/posts/:post_id/comments.:format?', com_ctl.index);
|
197 | app.post('/posts/:post_id/comments.:format?', com_ctl.create);
|
198 | app.get('/posts/:post_id/comments/:id.:format?', com_ctl.show);
|
199 | app.put('/posts/:post_id/comments/:id.:format?', com_ctl.update);
|
200 | app.delete('/posts/:post_id/comments/:id.:format?', com_ctl.destroy);
|
201 | app.get('/posts/:post_id/comments/:id/edit.:format?', com_ctl.edit);
|
202 | ```
|
203 |
|
204 | and you can more finely tune the resources to specify certain actions, middleware, and other. Here are example routes for [my blog][1]:
|
205 |
|
206 | ```js
|
207 | exports.routes = function (map) {
|
208 | map.get('/', 'posts#index');
|
209 | map.get(':id', 'posts#show');
|
210 | map.get('sitemap.txt', 'posts#map');
|
211 |
|
212 | map.namespace('admin', function (admin) {
|
213 | admin.resources('posts', {middleware: basic_auth, except: ['show']}, function (post) {
|
214 | post.resources('comments');
|
215 | post.get('likes', 'posts#likes')
|
216 | });
|
217 | });
|
218 | };
|
219 | ```
|
220 |
|
221 | since version 0.2.0, it is possible to use generic routes:
|
222 |
|
223 | ```js
|
224 | exports.routes = function (map) {
|
225 | map.get(':controller/:action/:id');
|
226 | map.all(':controller/:action');
|
227 | };
|
228 | ```
|
229 |
|
230 | if you have `custom_controller` with `test` action inside it you can now do:
|
231 |
|
232 | ```
|
233 | GET /custom/test
|
234 | POST /custom/test
|
235 | GET /custom/test/1 // also sets params.id to 1
|
236 | ```
|
237 |
|
238 | for debugging routes described in `config/routes.js` you can use `compound routes` command:
|
239 |
|
240 | ```
|
241 | $ compound routes
|
242 | GET / posts#index
|
243 | GET /:id posts#show
|
244 | sitemap.txt GET /sitemap.txt posts#map
|
245 | adminPosts GET /admin/posts.:format? admin/posts#index
|
246 | adminPosts POST /admin/posts.:format? admin/posts#create
|
247 | newAdminPost GET /admin/posts/new.:format? admin/posts#new
|
248 | editAdminPost GET /admin/posts/:id/edit.:format? admin/posts#edit
|
249 | adminPost DELETE /admin/posts/:id.:format? admin/posts#destroy
|
250 | adminPost PUT /admin/posts/:id.:format? admin/posts#update
|
251 | likesAdminPost PUT /admin/posts/:id/likes.:format? admin/posts#likes
|
252 | ```
|
253 |
|
254 | Filter by method:
|
255 |
|
256 | ```
|
257 | $ compound routes GET
|
258 | GET / posts#index
|
259 | GET /:id posts#show
|
260 | sitemap.txt GET /sitemap.txt posts#map
|
261 | adminPosts GET /admin/posts.:format? admin/posts#index
|
262 | newAdminPost GET /admin/posts/new.:format? admin/posts#new
|
263 | editAdminPost GET /admin/posts/:id/edit.:format? admin/posts#edit
|
264 | ```
|
265 |
|
266 | Filter by helper name:
|
267 |
|
268 | ```
|
269 | $ compound routes Admin
|
270 | newAdminPost GET /admin/posts/new.:format? admin/posts#new
|
271 | editAdminPost GET /admin/posts/:id/edit.:format? admin/posts#edit
|
272 | likesAdminPost PUT /admin/posts/:id/likes.:format? admin/posts#likes
|
273 | ```
|
274 |
|
275 |
|
276 | Helpers
|
277 | -------
|
278 |
|
279 | In addition to regular helpers `linkTo`, `formFor`, `javascriptIncludeTag`, `formFor`, etc. there are also helpers for routing: each route generates a helper method that can be invoked in a view:
|
280 |
|
281 | ```html
|
282 | <%- link_to("New post", newAdminPost) %>
|
283 | <%- link_to("New post", editAdminPost(post)) %>
|
284 | ```
|
285 |
|
286 | generates output:
|
287 |
|
288 | ```html
|
289 | <a href="/admin/posts/new">New post</a>
|
290 | <a href="/admin/posts/10/edit">New post</a>
|
291 | ```
|
292 |
|
293 | Controllers
|
294 | -----------
|
295 |
|
296 | The controller is a module containing the declaration of actions such as this:
|
297 |
|
298 | ```js
|
299 | beforeFilter(loadPost, {only: ['edit', 'update', 'destroy']});
|
300 |
|
301 | action('index', function () {
|
302 | Post.allInstances({order: 'created_at'}, function (collection) {
|
303 | render({ posts: collection });
|
304 | });
|
305 | });
|
306 |
|
307 | action('create', function () {
|
308 | Post.create(req.body, function () {
|
309 | redirect(pathTo.adminPosts);
|
310 | });
|
311 | });
|
312 |
|
313 | action('new', function () {
|
314 | render({ post: new Post });
|
315 | });
|
316 |
|
317 | action('edit', function () {
|
318 | render({ post: request.post });
|
319 | });
|
320 |
|
321 | action('update', function () {
|
322 | request.post.save(req.locale, req.body, function () {
|
323 | redirect(pathTo.adminPosts);
|
324 | });
|
325 | });
|
326 |
|
327 | function loadPost () {
|
328 | Post.find(req.params.id, function () {
|
329 | request.post = this;
|
330 | next();
|
331 | });
|
332 | }
|
333 | ```
|
334 |
|
335 | ## Generators ##
|
336 |
|
337 | Compound offers several built-in generators: for a model, controller and for
|
338 | initialization. Can be invoked as follows:
|
339 |
|
340 | ```js
|
341 | compound generate [what] [params]
|
342 | ```
|
343 |
|
344 | `what` can be `model`, `controller` or `scaffold`. Example of controller generation:
|
345 |
|
346 | ```
|
347 | $ compound generate controller admin/posts index new edit update
|
348 | exists app/
|
349 | exists app/controllers/
|
350 | create app/controllers/admin/
|
351 | create app/controllers/admin/posts_controller.js
|
352 | create app/helpers/
|
353 | create app/helpers/admin/
|
354 | create app/helpers/admin/posts_helper.js
|
355 | exists app/views/
|
356 | create app/views/admin/
|
357 | create app/views/admin/posts/
|
358 | create app/views/admin/posts/index.ejs
|
359 | create app/views/admin/posts/new.ejs
|
360 | create app/views/admin/posts/edit.ejs
|
361 | create app/views/admin/posts/update.ejs
|
362 | ```
|
363 |
|
364 | Currently it generates only `*.ejs` views
|
365 |
|
366 | Models
|
367 | ------
|
368 |
|
369 | Checkout [JugglingDB][2] docs to see how to work with models.
|
370 |
|
371 | CompoundJS Event model
|
372 | ----------------------
|
373 |
|
374 | Compound application loading process supports following events to be attached
|
375 | (in chronological order):
|
376 |
|
377 | 1. configure
|
378 | 2. after configure
|
379 | 3. routes
|
380 | 4. extensions
|
381 | 5. after extensions
|
382 | 6. structure
|
383 | 7. models
|
384 | 8. initializers
|
385 |
|
386 | REPL console
|
387 | ------------
|
388 |
|
389 | To run REPL console use command
|
390 |
|
391 | ```sh
|
392 | compound console
|
393 | ```
|
394 |
|
395 | or its shortcut
|
396 |
|
397 | ```sh
|
398 | compound c
|
399 | ```
|
400 |
|
401 | It's just simple node-js console with some Compound bindings, e.g. models. Just one note
|
402 | about working with console: Node.js is asynchronous by its nature, and it's great
|
403 | but it made console debugging much more complicated, because you should use callbacks
|
404 | to fetch results from the database, for example. I have added one useful method to
|
405 | simplify async debugging using compound console. It's named `c`. You can pass it
|
406 | as a parameter to any function requiring callbacks, and it will store parameters passed
|
407 | to the callback as variables `_0, _1, ..., _N` where N is index in `arguments`.
|
408 |
|
409 | Example:
|
410 |
|
411 | ```
|
412 | $ compound c
|
413 | compound> User.find(53, c)
|
414 | Callback called with 2 arguments:
|
415 | _0 = null
|
416 | _1 = [object Object]
|
417 | compound> _1
|
418 | { email: [Getter/Setter],
|
419 | password: [Getter/Setter],
|
420 | activationCode: [Getter/Setter],
|
421 | activated: [Getter/Setter],
|
422 | forcePassChange: [Getter/Setter],
|
423 | isAdmin: [Getter/Setter],
|
424 | id: [Getter/Setter] }
|
425 | ```
|
426 |
|
427 | Localization
|
428 | ------------
|
429 |
|
430 | To add another language to app just create a .yml file in `config/locales`,
|
431 | for example `config/locales/jp.yml`, copy contents of `config/locales/en.yml` to new
|
432 | file and rename root node (`en` to `jp` in that case), also in `lang` section rename
|
433 | `name` to Japanese (for example).
|
434 |
|
435 | Next step - rename email files in `app/views/emails`, copy all files `*.en.html`
|
436 | and `*.en.text` to `*.jp.html` and `*.jp.text` and translate new files.
|
437 |
|
438 | NOTE: translation can contain `%` symbol(s), that means variable substitution
|
439 |
|
440 | If you don't need locales support you can turn it off in `config/environment`:
|
441 |
|
442 | ```js
|
443 | app.set('i18n', 'off');
|
444 | ```
|
445 |
|
446 | Logger
|
447 | -----
|
448 |
|
449 | ```js
|
450 | app.set('quiet', true); // force logger to log into `log/#{app.settings.env}.log`
|
451 | compound.logger.write(msg); // to log message
|
452 | ```
|
453 |
|
454 | setup custom log dir:
|
455 |
|
456 | ```javascript
|
457 | app.get('log dir', '/var/log/compound-app/');
|
458 | ```
|
459 |
|
460 | Configuring
|
461 | ===========
|
462 |
|
463 | Compound has some configuration options allows to customize app behavior
|
464 |
|
465 | eval cache
|
466 | ----------
|
467 |
|
468 | Enable controller caching, should be turned on in prod. In development mode,
|
469 | disabling the cache allows the avoidance of server restarts after each model/controller change.
|
470 |
|
471 | ```js
|
472 | app.disable('eval cache'); // in config/environments/development.js
|
473 | app.enable('eval cache'); // in config/environments/production.js
|
474 | ```
|
475 |
|
476 | model cache
|
477 | -----------
|
478 |
|
479 | Same option for models. When disabled, model files evaluated per each request.
|
480 |
|
481 | ```js
|
482 | app.disable('model cache'); // in config/environments/development.js
|
483 | ```
|
484 |
|
485 | view cache
|
486 | ----------
|
487 |
|
488 | Express.js option, enables view caching.
|
489 |
|
490 | ```js
|
491 | app.disable('view cache'); // in config/environments/development.js
|
492 | ```
|
493 |
|
494 | quiet
|
495 | -----
|
496 |
|
497 | Write logs to `log/NODE_ENV.log`
|
498 |
|
499 | ```js
|
500 | app.set('quiet', true); // in config/environments/test.js
|
501 | ```
|
502 |
|
503 | merge javascripts
|
504 | -----------------
|
505 |
|
506 | Join all javascript files listed in `javascript_include_tag` into one
|
507 |
|
508 | ```js
|
509 | app.enable('merge javascripts'); // in config/environments/production.js
|
510 | ```
|
511 |
|
512 | merge stylesheets
|
513 | -----------------
|
514 |
|
515 | Join all stylesheet files listed in `stylesheets_include_tag` into one
|
516 |
|
517 | ```js
|
518 | app.enable('merge stylesheets'); // in config/environments/production.js
|
519 | ```
|
520 |
|
521 | ## Custom tools
|
522 |
|
523 | Put your function to ./app/tools/toolname.js to be able to run it within application
|
524 | environment as `compound toolname` command via CLI. See example tool in generated
|
525 | example: ./app/tools/dabatase.js
|
526 |
|
527 | Optionally you can specify some usage information on your function to be able to see
|
528 | it in list of available commands (using `compound` command).
|
529 |
|
530 | ```javascript
|
531 | module.exports.help = {
|
532 | shortcut: 'db',
|
533 | usage: 'db [migrate|update]',
|
534 | description: 'Migrate or update database(s)'
|
535 | };
|
536 | ```
|
537 |
|
538 |
|
539 | MIT License
|
540 | ===========
|
541 |
|
542 | Copyright (C) 2011 by Anatoliy Chakkaev <mail [åt] anatoliy [døt] in>
|
543 |
|
544 | Permission is hereby granted, free of charge, to any person obtaining a copy
|
545 | of this software and associated documentation files (the "Software"), to deal
|
546 | in the Software without restriction, including without limitation the rights
|
547 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
548 | copies of the Software, and to permit persons to whom the Software is
|
549 | furnished to do so, subject to the following conditions:
|
550 |
|
551 | The above copyright notice and this permission notice shall be included in
|
552 | all copies or substantial portions of the Software.
|
553 |
|
554 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
555 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
556 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
557 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
558 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
559 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
560 | THE SOFTWARE.
|
561 |
|
562 | [1]: http://anatoliy.in
|
563 | [2]: https://github.com/1602/jugglingdb
|
564 |
|
565 |
|
566 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/1602/compound/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
567 |
|