Introduction

Jamrock will enable you to write web apps once, you won't need to deal with back-end vs front-end nuances anymore!

The idea is straight-forward: run everything on the server-side.

Well, if it's possible to use JavaScript, some stuff may run on the browser too!

The building blocks

We have a couple of concepts to learn before digging:

  1. Routes are the entry-point for our application, and they are defined through pages or middleware.
  2. Route handlers are taken from the default export object on evaluated modules.
  3. The Request object is available through the jamrock:conn module.
  4. The Response is calculated by the framework.

Routes

Files ending with +page.html will be used to declare routes, they're transformed using the following rules:

Filename Route
pages/index+page.html /
pages/login+page.html /login
pages/(lang).blog+page.html /:lang?/blog
pages/($lang).blog+page.html /:lang?/blog
pages/hello.[name]+page.html /hello/:name
pages/posts/$post_id+page.html /posts/:post_id
pages/_site/sitemap[.xml]+page.html /sitemap.xml
pages/_site/articles/[...slug]+page.html /articles/*slug

Path parameters can be declared as $param or [param], optional parameters as ($param) or just (param), and catch-all parameters as [...param].

Segments are taken from nested folders or . separators, any of them starting with _ are just ignored from final paths.

Extensions can be preserved by using the [.ext] syntax.

Handlers

Pages can declare its own routes as well method handlers, to allow a certain method just set its value as true, e.g.

<script>
  export default {
    // middleware to invoke, see below
    use: ['csrf'],

    // allow for POST requests, no action
    POST: true,

    // action for DELETE requests
    DELETE() {
      // do something
    },

    // route-handlers for this component
    ['GET /:article_id']({ article_id }) {
      console.log({ article_id });
    },

    catch(e) {
      // handle error
    },
    finally() {
      // this always run
    },

    someAction() {
      // used on form actions
    },
  };
</script>

By default all pages will respond to GET requests, depending on their handlers they can respond to other methods.

If you don't want to execute certain page through the GET method just use GET: false to disable it.

Handlers using the syntax ['METHOD /path/with/:params'] are also registered as routes for the page, all declared parameters will be passed as arguments.

If you declare a catch or finally handler they'll be called as result of evaluating the requested handlers.

Additional handlers may be invoked if they match a requested action, usually from a <form action="?/someAction"> declaration.

In some cases you may want to run some code prior executing your handlers, to enable such behavior you must declare a use property.

Then, define some functions through a +server.mjs script, e.g.

export function http(conn) {
  // `http` handler executes on every request!
}

export function csrf(conn) {
  conn.req.csrfProtect();
}

export default {
  ['GET /some/:stuff'](conn) {
    console.log('Got', conn.params.stuff);
  },

  catch(e, conn) {
    // do something
  },
  finally(response, conn) {
    return response;
  }
};

This way you can setup shared behaviour in your applications, like authentication, shared props or state, etc.

  • Routes declared on the export default object are evaluated if they match, here is where you need to place api-routes as they don't require a page to exists.
  • The +server.mjs file can be placed at any level within the pages directory, following the same strategy as +layout.html or +error.html resolution.
  • These functions will receive the jamrock:conn first, any given options will be passed as the second argument. Those options should be set like this, e.g. use: [['name', { ... }]]

You can define catch and finally handlers on the export default object as well, they'll receive the error/response and connection respectively.

Make sure you return the given or modified response argument in your finally handler, the framework relies on this value to build the final response.

Request

In order to retrieve more stuff from the request you'll need to access the jamrock:conn module, e.g.

<script>
  import { method, headers, redirect } from 'jamrock:conn';

  if (method === 'GET' && !headers.has('token')) {
    redirect('/login');
  }
</script>

<h1>It works.</h1>

Modules starting with jamrock: are available only within page components.

Requests to page components will always render something, however redirect calls can stop any further rendering.

Below is a list of all sort of things you may use:

Available properties

  • req — the original Request object
  • store — reference to shared Map store
  • methodGET | PUT | POST | PATCH | DELETE
  • server — instantiated server object
  • status_code — get/set the response status code
  • resp_body — get/set the response body
  • base_url — get/set the <base href="/" /> path
  • cookies — request cookies as object
  • headers — request headers as object
  • session — saved session from store
  • options — framework options
  • abortedtrue if request has ended
  • params — mixed path, query and body params
  • path_info — list of path segments
  • path_params — route parameters
  • body_params — request body as object
  • request_path — requested url's pathname
  • query_string — requested url's query string
  • query_params — requested url's query as object
  • csrf_token — calculated token for the request
  • resp_cookies — response cookies (readonly)
  • resp_headers — response headers (readonly)
  • has_bodytrue if the response has a body value
  • has_statustrue if the response has a status code
  • is_xhrtrue if the request is XMLHttpRequest
  • env — safe copy of process.env (readonly)

Available methods

  • cookie(key, value, options) — set response cookies
  • header(key, value) — set response headers
  • redirect(url, code) — ends request with a redirection
  • flash(group, message) — writes to the session flash
  • raise(code, message) — ends the request as failure
  • protect(value) — decorates an unsafe value
  • unsafe(value)true if value is already unsafe
  • toJSON() — serialized verson of the conn object (safe)

Unsafe values are omitted if found during the rendering of page components, it prevents from leaking sensitive values by mistake.

Response

We have server routes, actions, handlers and middleware.

They all are functions and they can return anything:

  • number — just the status-code, without body
  • string — just the body, ends with a 200 OK
  • { ... } — object-like values will be sent as JSON
  • [number, string, { ... }] — status, body, and headers
  • new Response(string | null, ...) — standarized Response

In turn, page components will return an AST that can be serialized as HTML or sent as JSON.

If you want to return an array, like a list of values, use an object with a property containing its value instead.

Otherwise, the framework will try to extract the status, body, and headers parameters from your value.