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:
- Routes are the entry-point for our application, and they are defined through pages or middleware.
- Route handlers are taken from the
default export
object on evaluated modules. - The
Request
object is available through thejamrock:conn
module. - 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
orfinally
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 yourfinally
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 originalRequest
objectstore
— reference to sharedMap
storemethod
—GET
|PUT
|POST
|PATCH
|DELETE
server
— instantiated server objectstatus_code
— get/set the response status coderesp_body
— get/set the response bodybase_url
— get/set the<base href="/" />
pathcookies
— request cookies as objectheaders
— request headers as objectsession
— saved session from storeoptions
— framework optionsaborted
—true
if request has endedparams
— mixed path, query and body paramspath_info
— list of path segmentspath_params
— route parametersbody_params
— request body as objectrequest_path
— requested url's pathnamequery_string
— requested url's query stringquery_params
— requested url's query as objectcsrf_token
— calculated token for the requestresp_cookies
— response cookies (readonly)resp_headers
— response headers (readonly)has_body
—true
if the response has a body valuehas_status
—true
if the response has a status codeis_xhr
—true
if the request isXMLHttpRequest
env
— safe copy ofprocess.env
(readonly)
Available methods
cookie(key, value, options)
— set response cookiesheader(key, value)
— set response headersredirect(url, code)
— ends request with a redirectionflash(group, message)
— writes to the session flashraise(code, message)
— ends the request as failureprotect(value)
— decorates an unsafe valueunsafe(value)
—true
if value is already unsafetoJSON()
— serialized verson of theconn
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 bodystring
— just the body, ends with a200 OK
{ ... }
— object-like values will be sent as JSON[number, string, { ... }]
— status, body, and headersnew Response(string | null, ...)
— standarizedResponse
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
, andheaders
parameters from your value.