The quickest way to feel at home in a Noderyx project is to learn where things live. This site was generated with npm create noderyxcrm, and every folder has one clear job. In this tour we walk through the layout, then follow a real request through it.
The folders at a glance
server.js: where everything is wired together
The entry point loads .env, reads noderyx.config.js, and creates the application with noderyx(). It then registers shared services with app.provide, so any controller can ask for them by name.
const app = noderyx({ views, public: publicDir, security }); const cms = await CmsStore.create(config); app.provide("cms", cms); app.provide("themes", new ThemeManager()); app.use(requireCmsAuthentication); registerWebRoutes(app); registerCmsRoutes(app);
The order matters. Services are provided first. The authentication middleware runs before any route, and only then are routes registered. Local plugins under packages/ load last, so they can add routes or services of their own.
Routes map URLs to controller actions
routes/web.js is short enough to read in one sitting. Each line pairs an HTTP method and path with a controller action:
app.get("/", CmsController.handle("home")); app.get("/post/:slug", CmsController.handle("post")); app.post("/post/:slug/comments", CmsController.handle("addComment")); app.get("/category/:slug", CmsController.handle("category"));
Named segments such as :slug arrive in the controller as this.params.slug. The admin routes in routes/cms.js all live under one CMS_PATH constant, /nodecms, which makes the namespace easy to protect in one place.
Controllers decide what happens
A controller extends the framework's Controller class. Its actions read the request through this.params, this.query, and this.body. They fetch services with this.service(), and they respond with this.render(), this.json(), or this.abort().
export class CmsController extends Controller { store() { return this.service("cms"); } async post() { const post = await this.store().findPublished(this.params.slug, "post"); if (!post) return this.abort(404); return this.render("site/post", { post }); } }
Services hold the real logic
Controllers stay thin because services do the work. CmsStore reads and writes all content. ThemeManager decides which template and stylesheet the active theme uses. PluginManager tracks which local packages are enabled. Because services are plain classes, the test suite can exercise them directly, without starting a server.
Views are Noderframe templates
Templates in resources/views use indentation instead of closing tags. A line starts with an element and its classes, then attributes, then quoted text. Control flow uses if, else, and for.
article.article-wrap h1 "{{post.title}}" p.article-lead "{{post.excerpt}}" if post.featuredImage img.article-image src="{{post.featuredImage}}" alt="{{post.title}}" for item in navigation a href="{{item.url}}" "{{item.label}}"
Values inside {{ }} are escaped on output. This means text from the CMS cannot inject markup into the page.
Follow one request
- A reader opens /post/noderyx-project-structure.
- requireCmsAuthentication sees a public path and calls next().
- routes/web.js matches /post/:slug and calls CmsController.post.
- The action asks CmsStore for the published post with that slug.
- ThemeManager returns the active theme's post template, site/post.
- The template renders the post's blocks and comments into HTML.
The scripts you will use every day
Open routes/web.js, pick one route, and trace it to its controller action and template. Ten minutes of tracing teaches more than an hour of reading folder names.
Comments