├── .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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = ""; 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 = ""; 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 }); --------------------------------------------------------------------------------