node package manager
Love JavaScript? Your insights can make it even better. Take the 2017 JavaScript Ecosystem Survey »



Logo made by Yana Smirnova

npm version


npm install -g pjs-cli



By default, pjs will listen on the port 8080 (or process.env.PORT) and read the current folder. You can change this settings in http://localhost/_/, take a look at the usage documentation.

Inside the folder where pjs is reading you can prototype any node.js applications thanks to the .pjs files (see examples below).

For using pjs as a daemon, your can use these commands:

  • pjs start (start pjs as a daemon)
  • pjs stop
  • pjs restart
  • pjs status


To launch the examples on your computer, install first pjs-cli and then :

git clone
cd pjs/examples/

You can visit http://localhost:8080 too run all the examples presented below.

What are these .pjs files ?

It's 90% of EJS templates with a done() method for making every asynchronous operations possible! (take a look at the examples #2)

Example 1 - Basis

Let's say that I have a file name hello.pjs in my current folder:

<% var foo = 'PJS'; %>
Hello <%= foo %>!

I launch the cli in my current folder: pjs

Then, I visit : http://localhost:8080/hello.pjs

Result: Hello PJS!

Example 2 - Asynchronous

What about if we want something a little bit more asynchronous?

File async.pjs:

var request = require('request'),
request.get('', function (err, res, body) {
  // body = { "name": "PJS" }
  name = JSON.parse(body).name;
Hello <%= name %>!

As you can see, I can require a module, for that, please make sure to run npm install request in the current directory before visiting http://localhost:8080/async.pjs

The result will be : Hello PJS!

Notice the done(); method, it is important here for PJS to wait until the request is completed and the name set before going further in the template.

Example 3 - Includes

You may also want to include other .pjs files, that's why you can use the <% include your_file.pjs %> directly in your templates.

File include.pjs:

<p>I'm including hello.pjs</p>
<%- include hello.pjs %>

Result on http://localhost:8080/include.pjs

I'm including hello.pjs
Hello bar!

Actually, only this pre-processor include works. <%- include(file, { ... }); %> is not available yet.

Example 4 - REQUEST

Inside each .pjs file, you have access of the REQUEST variable. It contains some of the properties listed from Express (;

File: request.pjs

<% if (REQUEST.method === 'POST') { %>
  <p><b>New todo:</b> <%= REQUEST.body.todo %></p>
<% } %>
<form method="post">
  <input type="text" name="todo" placeholder="Learn Piano..." />
  <button type="submit">Add todo</button>

If you fill the input and click on "Add todo", the condition will pass and "New todo:" will be displayed on the screen with the content of the input.

List of the properties available in REQUEST:

- baseUrl
- body
- headers
- hostname
- fresh
- ip
- ips
- method
- originalUrl
- path
- protocol
- query
- secure
- stale
- subdomains
- url
- xhr


  • REQ is equal to REQUEST.
  • FORM which is REQ.body merge into REQ.query
  • METHOD is equal to REQUEST.method

Example 5 - RESPONSE

Sometimes you want to send back a custom status code or even a custom header. You can use the RESPONSE (alias: RES) object for this, here the current method available:

- .status(code) // Set the HTTP status for the response
- .header(field [, value]) // Same as res.set() for Express
- .type(type) // Same as res.type() for Express

File response.pjs:

RESPONSE.header('Custom', 'Hi!');
I'm a page with a custom status code and header!

If you go on http://localhost:8080/response.pjs with POSTMAN, you will see the status code 404 with the custom header and the Content-Type set to plain/text.

Example 6 - SESSION

In your PJS templates, you have also access to SESSION. This is useful for creating small apps for prototyping.

Here a Todo app with the use of the sessions with PJS.

File session.pjs:

SESSION.todos = SESSION.todos || [];
if (REQUEST.method === 'POST' && REQUEST.body.todo) {
if (REQUEST.method === 'GET' && REQUEST.query.index) {
  SESSION.todos = SESSION.todos.filter(function (todo, index) {
    return index !== Number(REQUEST.query.index);
<form method="post">
  <input type="text" name="todo" placeholder="Learn Piano..." />
  <button type="submit">Add todo</button>
  <% SESSION.todos.forEach(function (todo, index) { %>
    <li><%= todo %> - <a href="?index=<%- index %>">x</a></li>
  <% }); %>

Go to http://localhost:8080/session.pjs to se it working.

SESSION is equivalent of req.session in Express. PJS use session-file-store to persist the sessions in files, so you can restart pjs without worrying about the sessions.

Example 7 - JSON

For sending back a JSON object, simply display it with <%- yourJSON %>.

File json.pjs:

<%- FORM %>

FORM is an alias of REQ.body merged into REQ.query.

Try to visit http://localhost:8080/json?foo=bar&why=not

As you can see in the url, the .jps is optional.

Example 8 - Files

PJS let you upload files via multer. The maximum number of files is 10 and each file size 5 MB. The files are stored in the /tmp folder and are automatically deleted when the page is rendered.

You can access the files via REQUEST.files. Important, the form as to be multipart (multipart/form-data).

File files.pjs:

<% if (METHOD === 'POST' && REQ.files) { %>
<pre><%- JSON.stringify(REQ.files, undefined, 2) %></pre>
<% } %>
<form method="post" enctype="multipart/form-data">
  <p>File: <input type="file" name="file" /></p>
  <button type="submit">Upload</button>

You can go on http://localhost:8080/files.pjs, choose a file and click Upload, you will see all the data inside REQUEST.files.


    "fieldname": "file",
    "originalname": "pjs-image.jpg",
    "encoding": "7bit",
    "mimetype": "image/jpeg",
    "destination": "/tmp",
    "filename": "b52fb2303738fa17c0a2c8593de76798",
    "path": "/tmp/b52fb2303738fa17c0a2c8593de76798",
    "size": 71448

About PJS

As said before, PJS is mostly for quick prototyping and has no use case for production.

The idea was born after reading this article by VJEUX:


  • Trying vhost for rendering different folders (maybe not using res.render instead with a vhost middleware?)
  • Handling 404, 500 errors via templates (404.pjs, 500.pjs) if exists, use: app.use(function(err, req, res, next) {...});
  • Config WinstonJS log system
  • ./.pjs/config.json have a lastUpdate field to know on the ping when the recluster has reloaded
  • Add tests + jslint (start server on test/pjs/ + call request and check source code) + Travis + + Bithound (dependencies + code) +
  • Documentation with gitbook
  • Central UI (only links to the modules): url/_ + Add basic auth on _ and _/*
  • UI for configuring PJS (url/_/config): Authentification, Server (default port, proxy, SSL, folder for public views + for each vhost), Upload (nbFiles, fileSize, folder), Sessions (Secret key, Store (Mongo/Redis/Memory/Files + config), Sockets (enable/disable + socket config file), Errors Handler (Winstog log files, Sentry config, etc.)
  • UI for configuring the Routes (url/_/routes): Router /api/users/:id -> render file api/users.pjs + add req.params inside REQ + FORM
  • UI for configuring the Node modules (url/_/npm): Listing installed modules, Install/Update/Remove (,version,repository,description,author)
  • UI for viewing/searching the logs (url/_/logs)
  • Finding a way of using the sockets and configuring them server-side (path of the file to use) + try/catch to avoid server error on startup
  • Video of presentation