├── .gitattributes
├── readme.md
├── ssr-example-01
├── server.ts
└── views
│ ├── index.hbs
│ └── layouts
│ └── main.hbs
├── ssr-example-02
├── server.ts
└── views
│ ├── index.hbs
│ └── layouts
│ └── main.hbs
└── ssr-example-03
├── client.html
├── client.js
└── server.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Examples of SSR using Deno, Oak, and Handlebars
2 |
3 | This repo shows three examples of SSR, which are accompanied by [this blog post](https://deno.com/blog/back-to-the-ssr).
4 |
5 |
--------------------------------------------------------------------------------
/ssr-example-01/server.ts:
--------------------------------------------------------------------------------
1 | import { Application, Router } from "https://deno.land/x/oak@v11.1.0/mod.ts";
2 | import { Handlebars } from "https://deno.land/x/handlebars@v0.9.0/mod.ts";
3 |
4 | const dinos = ["Allosaur", "T-Rex", "Deno"];
5 | const handle = new Handlebars();
6 | const router = new Router();
7 |
8 | router.get("/", async (context) => {
9 | context.response.body = await handle.renderView("index", { dinos: dinos });
10 | });
11 |
12 | const app = new Application();
13 |
14 | app.use(router.routes());
15 | app.use(router.allowedMethods());
16 |
17 | await app.listen({ port: 8000 });
18 |
--------------------------------------------------------------------------------
/ssr-example-01/views/index.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#each dinos}}
3 | - {{this}}
4 | {{/each}}
5 |
--------------------------------------------------------------------------------
/ssr-example-01/views/layouts/main.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Dinosaurs
6 |
7 |
8 |
9 |
10 | {{{body}}}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ssr-example-02/server.ts:
--------------------------------------------------------------------------------
1 | import { Application, Router } from "https://deno.land/x/oak@v11.1.0/mod.ts";
2 | import { Handlebars } from "https://deno.land/x/handlebars@v0.9.0/mod.ts";
3 |
4 | const dinos = ["Allosaur", "T-Rex", "Deno"];
5 | const handle = new Handlebars();
6 | const router = new Router();
7 |
8 | router.get("/", async (context) => {
9 | context.response.body = await handle.renderView("index", { dinos: dinos });
10 | });
11 |
12 | router.post("/add", async (context) => {
13 | const { value } = await context.request.body({ type: "json" });
14 | const { item } = await value;
15 | dinos.push(item);
16 | context.response.status = 200;
17 | });
18 |
19 | const app = new Application();
20 |
21 | app.use(router.routes());
22 | app.use(router.allowedMethods());
23 |
24 | await app.listen({ port: 8000 });
25 |
--------------------------------------------------------------------------------
/ssr-example-02/views/index.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#each dinos}}
3 | - {{this}}
4 | {{/each}}
5 |
--------------------------------------------------------------------------------
/ssr-example-02/views/layouts/main.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Dinosaurs
6 |
7 |
8 |
9 |
10 | {{{body}}}
11 |
12 |
13 |
14 |
15 |
26 |
--------------------------------------------------------------------------------
/ssr-example-03/client.html:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/ssr-example-03/client.js:
--------------------------------------------------------------------------------
1 | let isFirstRender = true;
2 |
3 | // Simple HTML sanitization to prevent XSS vulnerabilities.
4 | function sanitizeHtml(text) {
5 | return text
6 | .replace(/&/g, "&")
7 | .replace(//g, ">")
9 | .replace(/"/g, """)
10 | .replace(/'/g, "'");
11 | }
12 |
13 | export async function render(document, dinos) {
14 | if (isFirstRender) {
15 | const jsonResponse = await fetch("http://localhost:8000/data");
16 | if (jsonResponse.ok) {
17 | const jsonData = await jsonResponse.json();
18 | const dinos = jsonData;
19 | let html = "";
20 | for (const item of dinos) {
21 | html += `- ${sanitizeHtml(item)}
`;
22 | }
23 | html += "
";
24 | html += "";
25 | document.body.innerHTML = html;
26 | isFirstRender = false;
27 | } else {
28 | document.body.innerHTML = "Something went wrong.
";
29 | }
30 | } else {
31 | let html = "";
32 | for (const item of dinos) {
33 | html += `- ${sanitizeHtml(item)}
`;
34 | }
35 | html += "
";
36 | document.querySelector("ul").outerHTML = html;
37 | }
38 | }
39 |
40 | export function addEventListeners() {
41 | document.querySelector("button").addEventListener("click", async () => {
42 | const item = document.querySelector("input").value;
43 | const dinos = Array.from(document.querySelectorAll("li"), e => e.innerText);
44 | dinos.push(item);
45 | const response = await fetch("/add", {
46 | method: "POST",
47 | headers: {
48 | "Content-Type": "application/json",
49 | },
50 | body: JSON.stringify({ item }),
51 | });
52 | if (response.ok) {
53 | render(document, dinos);
54 | } else {
55 | // In a real app, you'd want better error handling.
56 | console.error("Something went wrong.");
57 | }
58 | });
59 | }
60 |
--------------------------------------------------------------------------------
/ssr-example-03/server.ts:
--------------------------------------------------------------------------------
1 | import { Application } from "https://deno.land/x/oak@v11.1.0/mod.ts";
2 | import { Router } from "https://deno.land/x/oak@v11.1.0/mod.ts";
3 | import { DOMParser } from "https://deno.land/x/deno_dom@v0.1.36-alpha/deno-dom-wasm.ts";
4 | import { render } from "./client.js";
5 |
6 | const html = await Deno.readTextFile("./client.html");
7 | const dinos = ["Allosaur", "T-Rex", "Deno"];
8 | const router = new Router();
9 |
10 | router.get("/client.js", async (context) => {
11 | await context.send({
12 | root: Deno.cwd(),
13 | index: "client.js",
14 | });
15 | });
16 |
17 | router.get("/", (context) => {
18 | const document = new DOMParser().parseFromString(
19 | "",
20 | "text/html",
21 | );
22 | render(document, { dinos });
23 | context.response.type = "text/html";
24 | context.response.body = `${document.body.innerHTML}${html}`;
25 | });
26 |
27 | router.get("/data", (context) => {
28 | context.response.body = dinos;
29 | });
30 |
31 | router.post("/add", async (context) => {
32 | const { value } = await context.request.body({ type: "json" });
33 | const { item } = await value;
34 | dinos.push(item);
35 | context.response.status = 200;
36 | });
37 |
38 | const app = new Application();
39 |
40 | app.use(router.routes());
41 | app.use(router.allowedMethods());
42 |
43 | await app.listen({ port: 8000 });
--------------------------------------------------------------------------------