├── .github └── workflows │ └── ci.yml ├── LICENSE ├── Makefile ├── README.md ├── discord ├── demo.png ├── mod.ts └── readme.md ├── fetch ├── README.md ├── get.js └── post.js ├── issues ├── components │ ├── card.jsx │ ├── layout.jsx │ └── search.jsx ├── data │ └── repositories.js ├── mod.js ├── pages │ ├── 404.jsx │ ├── api │ │ └── issues.js │ └── home.jsx └── readme.md ├── json_html ├── README.md └── mod.js ├── post_request ├── mod.js └── readme.md ├── slack ├── cities.ts ├── demo.png ├── install.png ├── mod.ts └── readme.md ├── telegram ├── README.md ├── demo.png └── mod.ts └── yaus ├── mod.tsx ├── readme.md └── schema.gql /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Clone repository 13 | uses: actions/checkout@v3 14 | with: 15 | submodules: false 16 | persist-credentials: false 17 | 18 | - name: Install Deno 19 | run: |- 20 | curl -fsSL https://deno.land/x/install/install.sh | sh 21 | echo "$HOME/.deno/bin" >> $GITHUB_PATH 22 | 23 | - name: Format 24 | run: deno fmt --check 25 | 26 | - name: Lint 27 | run: deno lint --unstable 28 | 29 | - name: Type Check 30 | run: make check 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Deno Land Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Check TypeScript types. 2 | check: 3 | deno cache fetch/get.js 4 | deno cache fetch/post.js 5 | deno cache post_request/mod.js 6 | deno cache json_html/mod.js 7 | deno cache issues/mod.js 8 | deno cache discord/mod.ts 9 | deno cache slack/mod.ts 10 | deno cache yaus/mod.tsx 11 | deno cache telegram/mod.ts 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deno Deploy Examples 2 | 3 | This repository contains a list of examples for Deno Deploy. 4 | 5 | - [fetch](fetch) - Make outbound requests using the `fetch()` API. 6 | - [json_html](json_html) - Respond to requests with JSON or HTML. 7 | - [post_request](post_request) - Handle POST requests. 8 | - [slack](slack) - A Slack Slash Command example. 9 | - [discord](discord) - A Discord Slash Command example. 10 | - [yaus](yaus) - A URL shortener built on top of Deno Deploy and FaunaDB. 11 | - [issues](issues) - A server rendered (using JSX) website that displays issues 12 | with most comments of a GitHub repository. 13 | - [telegram](telegram) - A Telegram Bot Command example. 14 | -------------------------------------------------------------------------------- /discord/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denoland/deploy_examples/fd36d69fc020470c41cc095183c5c3f2e6a4c00f/discord/demo.png -------------------------------------------------------------------------------- /discord/mod.ts: -------------------------------------------------------------------------------- 1 | // Sift is a small routing library that abstracts the details like registering 2 | // a fetch event listener and provides a simple function (serve) that has an 3 | // API to invoke a funciton for a specific path. 4 | import { 5 | json, 6 | serve, 7 | validateRequest, 8 | } from "https://deno.land/x/sift@0.4.2/mod.ts"; 9 | // TweetNaCl is a cryptography library that we use to verify requests 10 | // from Discord. 11 | import nacl from "https://cdn.skypack.dev/tweetnacl@v1.0.3"; 12 | 13 | // For all requests to "/" endpoint, we want to invoke home() handler. 14 | serve({ 15 | "/": home, 16 | }); 17 | 18 | // The main logic of the Discord Slash Command is defined in this function. 19 | async function home(request: Request) { 20 | // validateRequest() ensures that a request is of POST method and 21 | // has the following headers. 22 | const { error } = await validateRequest(request, { 23 | POST: { 24 | headers: ["X-Signature-Ed25519", "X-Signature-Timestamp"], 25 | }, 26 | }); 27 | if (error) { 28 | return json({ error: error.message }, { status: error.status }); 29 | } 30 | 31 | // verifySignature() verifies if the request is coming from Discord. 32 | // When the request's signature is not valid, we return a 401 and this is 33 | // important as Discord sends invalid requests to test our verification. 34 | const { valid, body } = await verifySignature(request); 35 | if (!valid) { 36 | return json( 37 | { error: "Invalid request" }, 38 | { 39 | status: 401, 40 | }, 41 | ); 42 | } 43 | 44 | const { type = 0, data = { options: [] } } = JSON.parse(body); 45 | // Discord performs Ping interactions to test our application. 46 | // Type 1 in a request implies a Ping interaction. 47 | if (type === 1) { 48 | return json({ 49 | type: 1, // Type 1 in a response is a Pong interaction response type. 50 | }); 51 | } 52 | 53 | // Type 2 in a request is an ApplicationCommand interaction. 54 | // It implies that a user has issued a command. 55 | if (type === 2) { 56 | const { value } = data.options.find( 57 | (option: { name: string }) => option.name === "name", 58 | ); 59 | return json({ 60 | // Type 4 reponds with the below message retaining the user's 61 | // input at the top. 62 | type: 4, 63 | data: { 64 | content: `Hello, ${value}!`, 65 | }, 66 | }); 67 | } 68 | 69 | // We will return a bad request error as a valid Discord request 70 | // shouldn't reach here. 71 | return json({ error: "bad request" }, { status: 400 }); 72 | } 73 | 74 | /** Verify whether the request is coming from Discord. */ 75 | async function verifySignature( 76 | request: Request, 77 | ): Promise<{ valid: boolean; body: string }> { 78 | const PUBLIC_KEY = Deno.env.get("DISCORD_PUBLIC_KEY")!; 79 | // Discord sends these headers with every request. 80 | const signature = request.headers.get("X-Signature-Ed25519")!; 81 | const timestamp = request.headers.get("X-Signature-Timestamp")!; 82 | const body = await request.text(); 83 | const valid = nacl.sign.detached.verify( 84 | new TextEncoder().encode(timestamp + body), 85 | hexToUint8Array(signature), 86 | hexToUint8Array(PUBLIC_KEY), 87 | ); 88 | 89 | return { valid, body }; 90 | } 91 | 92 | /** Converts a hexadecimal string to Uint8Array. */ 93 | function hexToUint8Array(hex: string) { 94 | return new Uint8Array(hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16))); 95 | } 96 | -------------------------------------------------------------------------------- /discord/readme.md: -------------------------------------------------------------------------------- 1 | # Discord Slash Command 2 | 3 | A simple Discord Slash Command. 4 | 5 | demo of discord slash command 6 | 7 | Read [the tutorial](https://deno.com/deploy/docs/tutorial-discord-slash) to 8 | learn how to create and deploy a Discord Slash Command. 9 | -------------------------------------------------------------------------------- /fetch/README.md: -------------------------------------------------------------------------------- 1 | # fetch() examples 2 | 3 | The examples here demonstrate how to make fetch() requests. 4 | 5 | - [`GET`](get.js) - makes a GET request to GitHub API endpoint. 6 | - [`POST`](post.js) - makes a POST request to https://post.deno.dev (echoes the 7 | request body back). 8 | -------------------------------------------------------------------------------- /fetch/get.js: -------------------------------------------------------------------------------- 1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts"; 2 | 3 | async function handleRequest(_request) { 4 | // We pass the url as the first argument to fetch and an object with 5 | // additional info like headers, method, and body for POST requests as 6 | // the second argument. By default fetch makes a GET request, 7 | // so we can skip specifying method for GET requests. 8 | const response = await fetch("https://api.github.com/users/denoland", { 9 | headers: { 10 | // Servers use this header to decide on response body format. 11 | // "application/json" implies that we accept the data in JSON format. 12 | accept: "application/json", 13 | }, 14 | }); 15 | 16 | // The .ok property of response indicates that the request is 17 | // successful (status is in range of 200-299). 18 | if (response.ok) { 19 | // response.json() method reads the body and parses it as JSON. 20 | // It then returns the data in JavaScript object. 21 | const { name, login, avatar_url: avatar } = await response.json(); 22 | return new Response( 23 | JSON.stringify({ name, username: login, avatar }), 24 | { 25 | headers: { 26 | "content-type": "application/json; charset=UTF-8", 27 | }, 28 | }, 29 | ); 30 | } 31 | // fetch() doesn't throw for bad status codes. You need to handle them 32 | // by checking if the response.ok is true or false. 33 | // In this example we're just returning a generic error for simplicity but 34 | // you might want to handle different cases based on response status code. 35 | return new Response( 36 | JSON.stringify({ message: "couldn't process your request" }), 37 | { 38 | status: 500, 39 | headers: { 40 | "content-type": "application/json; charset=UTF-8", 41 | }, 42 | }, 43 | ); 44 | } 45 | 46 | console.log("Listening on http://localhost:8080"); 47 | await listenAndServe(":8080", handleRequest); 48 | -------------------------------------------------------------------------------- /fetch/post.js: -------------------------------------------------------------------------------- 1 | import { listenAndServe } from "https://deno.land/std@0.111.0/http/server.ts"; 2 | 3 | async function handleRequest(_request) { 4 | // For making a POST request we need to specify the method property 5 | // as POST and provide data to the body property in the same object. 6 | // https://post.deno.dev echoes data we POST to it. 7 | const response = await fetch("https://post.deno.dev", { 8 | method: "POST", 9 | headers: { 10 | // This headers implies to the server that the content of 11 | // body is JSON and is encoded using UTF-8. 12 | "content-type": "application/json; charset=UTF-8", 13 | }, 14 | body: JSON.stringify({ 15 | message: "Hello from Deno Deploy.", 16 | }), 17 | }); 18 | 19 | if (response.ok) { 20 | // The echo server returns the data back in 21 | const { 22 | json: { message }, 23 | } = await response.json(); 24 | return new Response(JSON.stringify({ message }), { 25 | headers: { 26 | "content-type": "application/json; charset=UTF-8", 27 | }, 28 | }); 29 | } 30 | 31 | return new Response( 32 | JSON.stringify({ message: "couldn't process your request" }), 33 | { 34 | status: 500, 35 | headers: { 36 | "content-type": "application/json; charset=UTF-8", 37 | }, 38 | }, 39 | ); 40 | } 41 | 42 | console.log("Listening on http://localhost:8080"); 43 | await listenAndServe(":8080", handleRequest); 44 | -------------------------------------------------------------------------------- /issues/components/card.jsx: -------------------------------------------------------------------------------- 1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts"; 2 | import { formatDistanceToNow } from "https://cdn.skypack.dev/pin/date-fns@v2.16.1-IRhVs8UIgU3f9yS5Yt4w/date-fns.js"; 3 | 4 | export default function Card({ 5 | url, 6 | title, 7 | state, 8 | comments, 9 | createdAt, 10 | closedAt, 11 | }) { 12 | return ( 13 |
14 | 15 |

{title}

16 |
17 |
18 | 26 | {state === "closed" 27 | ? ( 28 | 32 | ) 33 | : ( 34 | 38 | )} 39 | 40 |
41 | 46 | 50 | 51 | {comments} 52 |
53 | 54 | {state === "closed" 55 | ? `closed ${formatDistanceToNow(new Date(closedAt))} ago` 56 | : `opened ${formatDistanceToNow(new Date(createdAt))} ago`} 57 | 58 |
59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /issues/components/layout.jsx: -------------------------------------------------------------------------------- 1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts"; 2 | 3 | export default function Layout({ children }) { 4 | return ( 5 | 6 | 7 | 8 | 12 | 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /issues/components/search.jsx: -------------------------------------------------------------------------------- 1 | import { h } from "https://deno.land/x/sift@0.4.2/mod.ts"; 2 | 3 | /** Validate if the form value follows the appropriate 4 | * structure (/). */ 5 | function validateForm() { 6 | // deno-lint-ignore no-undef 7 | const repository = document.forms["search"]["repository"].value; 8 | if (repository.split("/").length !== 2) { 9 | alert( 10 | `Input should be in the form of 'owner/repository'. No forward slashes at the beginning or end.`, 11 | ); 12 | return false; 13 | } 14 | return true; 15 | } 16 | 17 | export default function Search() { 18 | return ( 19 |
20 |