|
1 | | -<div align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/denoland/deno_logo2/main/deno_dark.svg"> <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/denoland/deno_logo2/main/deno_light.svg"> <img alt="Deno App Logo" src="https://raw.githubusercontent.com/denoland/deno_logo2/main/deno_light.svg" width="120"> </picture> <h1>Deno App</h1> <p><b>A simple, extensible Deno-powered web server with custom routing and static file support.</b></p> <p> <a href="https://deno.com/"><img alt="Deno" src="https://img.shields.io/badge/deno-%5E1.0.0-black?logo=deno&logoColor=white"></a> <a href="#"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a> <a href="#"><img alt="Status: Active" src="https://img.shields.io/badge/status-active-brightgreen"></a> </p> </div><div align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/denoland/deno_logo2/main/deno_dark.svg"> <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/denoland/deno_logo2/main/deno_light.svg"> <img alt="Deno App Logo" src="https://raw.githubusercontent.com/denoland/deno_logo2/main/deno_light.svg" width="120"> </picture> <h1>Deno App</h1> <p><b>A simple, extensible Deno-powered web server with custom routing and static file support.</b></p> <p> <a href="https://deno.com/"><img alt="Deno" src="https://img.shields.io/badge/deno-%5E1.0.0-black?logo=deno&logoColor=white"></a> <a href="#"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a> <a href="#"><img alt="Status: Active" src="https://img.shields.io/badge/status-active-brightgreen"></a> </p> </div> |
| 1 | +<div align="center"> |
| 2 | + <img src="https://raw.githubusercontent.com/denoland/deno_logo2/main/deno_light.svg" width="100" alt="Deno logo"> |
| 3 | + <h1>Rest API From Scratch Using Deno</h1> |
| 4 | + <p><b>A simple, modular Deno-powered web server with custom routing and static file support.</b></p> |
| 5 | + <p> |
| 6 | + <img alt="Deno" src="https://img.shields.io/badge/deno-%5E1.0.0-black?logo=deno&logoColor=white"> |
| 7 | + <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"> |
| 8 | + <img alt="Status: Active" src="https://img.shields.io/badge/status-active-brightgreen"> |
| 9 | + </p> |
| 10 | +</div> |
2 | 11 |
|
3 | | -📋 Table of Contents |
4 | | -Features |
| 12 | +--- |
5 | 13 |
|
6 | | -Project Structure |
| 14 | +## 📋 Table of Contents |
7 | 15 |
|
8 | | -Getting Started |
| 16 | +- [Overview](#overview) |
| 17 | +- [Features](#features) |
| 18 | +- [Project Structure](#project-structure) |
| 19 | +- [Getting Started](#getting-started) |
| 20 | +- [API Endpoints](#api-endpoints) |
| 21 | +- [Routing Logic](#routing-logic) |
| 22 | +- [Configuration](#configuration) |
| 23 | +- [Source Files](#source-files) |
| 24 | +- [Contributing](#contributing) |
| 25 | +- [License](#license) |
| 26 | +- [Credits](#credits) |
9 | 27 |
|
10 | | -API Endpoints |
| 28 | +--- |
11 | 29 |
|
12 | | -Routing Logic |
| 30 | +## 📝 Overview |
13 | 31 |
|
14 | | -Configuration |
| 32 | +This project is a minimalist Deno web server featuring: |
15 | 33 |
|
16 | | -Screenshots |
| 34 | +- A custom router for clean HTTP GET route handling |
| 35 | +- Static file serving from nested directories |
| 36 | +- Basic user data API endpoints |
| 37 | +- An HTML landing page |
17 | 38 |
|
18 | | -Contributing |
| 39 | +It is designed for clarity, modularity, and easy extension, following a single-file, clean interface philosophy[8][11]. |
19 | 40 |
|
20 | | -License |
| 41 | +--- |
21 | 42 |
|
22 | | -Credits |
| 43 | +## ✨ Features |
23 | 44 |
|
24 | | -✨ Features |
25 | | -🚦 Custom lightweight router for HTTP GET routes |
| 45 | +- 🚦 **Custom lightweight router** for HTTP GET routes |
| 46 | +- 🗂️ **Static file serving** (including images) from nested directories |
| 47 | +- 👤 **User API endpoints** (`/users`, `/user/:id`) |
| 48 | +- 🏠 **HTML landing page** (`index.html`) |
| 49 | +- 🛠️ **Easy local development** and extension |
26 | 50 |
|
27 | | -🗂️ Static file serving (including images) from nested directories |
| 51 | +--- |
28 | 52 |
|
29 | | -👤 User API endpoints (/users, /user/:id) |
| 53 | +## 🗂️ Project Structure |
30 | 54 |
|
31 | | -🏠 HTML landing page (index.html) |
| 55 | +| File/Folder | Purpose | |
| 56 | +|-------------------|--------------------------------------------------------| |
| 57 | +| `main.ts` | Main server entrypoint, registers all routes | |
| 58 | +| `Router.ts` | Custom Router class for HTTP routes | |
| 59 | +| `StaticFile.ts` | Utility for serving static files (optional) | |
| 60 | +| `index.html` | Landing page served at `/` | |
| 61 | +| `deno.json` | Deno configuration and task definitions | |
| 62 | +| `log.json` | Example log output | |
32 | 63 |
|
33 | | -🛠️ Easy local development and extension |
| 64 | +--- |
34 | 65 |
|
35 | | -🗂️ Project Structure |
36 | | -File/Folder Purpose |
37 | | -main.ts Main server entrypoint, route registration |
38 | | -Router.ts Custom Router class for HTTP routes |
39 | | -StaticFile.ts Utility for serving static files (optional) |
40 | | -index.html Landing page served at / |
41 | | -Models/Users.ts User model (referenced in code) |
42 | | -deno.json Deno configuration and task definitions |
43 | | -log.json Example log output |
44 | | -🚀 Getting Started |
45 | | -Prerequisites |
46 | | -Deno v1.0+ installed |
| 66 | +## 🚀 Getting Started |
47 | 67 |
|
48 | | -Run the Development Server |
49 | | -text |
50 | | -deno task dev |
51 | | -or directly: |
| 68 | +### Prerequisites |
52 | 69 |
|
53 | | -text |
54 | | -deno run --watch --allow-net --allow-read --allow-write main.ts |
55 | | -Server runs on port 8000. |
| 70 | +- [Deno](https://deno.com/) v1.0+ installed |
56 | 71 |
|
57 | | -📡 API Endpoints |
58 | | -Method Path Description |
59 | | -GET / Serves the landing page |
60 | | -GET /users Returns all users as JSON |
61 | | -GET /user/:id Returns a single user by ID as JSON |
62 | | -GET /:main/:sub?/:filename Serves static PNG files from folders |
63 | | -🛣️ Routing Logic |
64 | | -All routing is managed by a custom Router class, supporting parameterized paths and method matching. |
| 72 | +### Run the Development Server |
65 | 73 |
|
66 | | -Static files (PNG images) can be served from nested directories via dynamic routes. |
| 74 | +`deno run --watch --allow-net --allow-read --allow-write main.ts` |
67 | 75 |
|
68 | | -Example: /images/avatar.png or /images/profile/avatar.png will serve the corresponding PNG file. |
| 76 | +`Server runs on **port 8000**.` |
69 | 77 |
|
70 | | -⚙️ Configuration |
71 | | -deno.json defines tasks and imports: |
| 78 | +--- |
72 | 79 |
|
73 | | -json |
| 80 | +## 📡 API Endpoints |
| 81 | + |
| 82 | +| Method | Path | Description | |
| 83 | +|--------|-----------------------------|------------------------------------------| |
| 84 | +| GET | `/` | Serves the landing page | |
| 85 | +| GET | `/users` | Returns all users as JSON | |
| 86 | +| GET | `/user/:id` | Returns a single user by ID as JSON | |
| 87 | +| GET | `/:main/:sub?/:filename` | Serves static PNG files from folders | |
| 88 | + |
| 89 | +--- |
| 90 | + |
| 91 | +## 🛣️ Routing Logic |
| 92 | + |
| 93 | +- All routing is managed by a custom `Router` class, supporting parameterized paths and method matching. |
| 94 | +- Static files (PNG images) can be served from nested directories via dynamic routes. |
| 95 | +- Example: `/images/avatar.png` or `/images/profile/avatar.png` will serve the corresponding PNG file. |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +## ⚙️ Configuration |
| 100 | + |
| 101 | +**deno.json** defines tasks and imports: |
| 102 | +```json |
74 | 103 | { |
75 | 104 | "tasks": { |
76 | | - "dev": "deno run --watch --allow-net --allow-read --allow-write main.ts" |
| 105 | + "dev": "deno run --watch --allow-net --allow-read --allow-write main.ts" |
77 | 106 | }, |
78 | 107 | "imports": { |
79 | | - "@std/assert": "jsr:@std/assert@1" |
| 108 | + "@std/assert": "jsr:@std/assert@1" |
80 | 109 | } |
81 | 110 | } |
82 | | -View deno.json for details |
| 111 | +``` |
| 112 | + |
| 113 | +[deno.json][1] |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## 📂 Source Files |
| 118 | + |
| 119 | +### `main.ts` |
| 120 | +```ts |
| 121 | +// Import the custom Router class for handling HTTP routes |
| 122 | +import {Router} from "./Router.ts"; |
| 123 | + |
| 124 | +// Import the User model for interacting with user data |
| 125 | +import {User} from "./Models/Users.ts"; |
| 126 | + |
| 127 | +// Create an instance of the Router to register routes |
| 128 | +const router = new Router() |
| 129 | + |
| 130 | + |
| 131 | +// Register a GET route for the root path ('/') |
| 132 | +// This serves the 'index.html' file as a readable stream |
| 133 | +router.get('/', async function () { |
| 134 | + const index = Deno.open('index.html'); |
| 135 | + const readFile = (await index).readable |
| 136 | + return new Response(readFile); |
| 137 | +}) |
| 138 | + |
| 139 | +// Register a GET route for '/users' |
| 140 | +// This response with a JSON array of all users |
| 141 | +router.get('/users', async function () { |
| 142 | + const users = JSON.stringify(await User.all()); |
| 143 | + return new Response(users, { |
| 144 | + headers : { |
| 145 | + 'content-type' : 'application/json' |
| 146 | + } |
| 147 | + }); |
| 148 | +}); |
| 149 | + |
| 150 | +// Register a GET route for '/user/:id' |
| 151 | +// This responds with a JSON object for a specific user by ID |
| 152 | +router.get('/user/:id',async function (req) { |
| 153 | + const user = await User.findById(Number(req.params?.id)) |
| 154 | + return new Response( JSON.stringify(user) , { |
| 155 | + headers : { |
| 156 | + 'content-type' : 'application/json' |
| 157 | + } |
| 158 | + }); |
| 159 | +}) |
| 160 | + |
| 161 | +// Register a GET route for serving static files from nested directories |
| 162 | +// Supports optional subfolder and returns the file as a PNG image |
| 163 | +router.get('/:main/:sub?/:filename', async function (req){ |
| 164 | + const mainFolder = req.params?.main |
| 165 | + const subFolder = req.params?.sub; |
| 166 | + const filename = req.params?.filename |
| 167 | + let readFile; |
| 168 | + |
| 169 | + if (subFolder == null) |
| 170 | + readFile = await Deno.readFile(`${mainFolder}/${filename}`); |
| 171 | + else |
| 172 | + readFile = await Deno.readFile(`${mainFolder}/${subFolder}/${filename}`); |
| 173 | + |
| 174 | + return new Response(readFile, { |
| 175 | + headers: { |
| 176 | + 'content-type': 'image/png', |
| 177 | + } |
| 178 | + }) |
| 179 | +}) |
| 180 | + |
| 181 | +// Example of how to serve static files using the StaticFile utility (currently commented out) |
| 182 | +/*StaticFile.serve('/:main/:sub?/:filename')*/ |
| 183 | + |
| 184 | +// The main request handler that delegates all requests to the router |
| 185 | +function handler(req : Request): Promise<Response> | Response { |
| 186 | + return router.route(req) |
| 187 | +} |
| 188 | + |
| 189 | +// Start the Deno HTTP server on port 8000 using the handler function |
| 190 | +Deno.serve({port: 8000}, handler) |
| 191 | +``` |
| 192 | +[main.ts][4] |
| 193 | + |
| 194 | +--- |
| 195 | + |
| 196 | +### `Router.ts` |
| 197 | + |
| 198 | +```ts |
| 199 | +// Import the Routes type, which describes the structure of route definitions |
| 200 | +import type {Routes} from "./types/Routes.ts"; |
| 201 | + |
| 202 | +// Import the Req type, representing the shape of the request object used by the router |
| 203 | +import type { Req } from "./types/Req.ts"; |
| 204 | + |
| 205 | +// Define the Router class to handle route registration and request routing |
| 206 | +export class Router { |
| 207 | + |
| 208 | + // Private array to store all registered routes |
| 209 | + private routes: Routes[] = [] |
| 210 | + |
| 211 | + // Register a GET route with a path and its callback handler |
| 212 | + get(path: string, callback: (req: Req) => Promise<Response> | Response) { |
| 213 | + this.routes.push({method: "GET", path: path, callback: callback}); |
| 214 | + } |
| 215 | + |
| 216 | + // Main method to match incoming requests to registered routes and execute the appropriate callback |
| 217 | + route(req: Req): Response | Promise<Response> { |
| 218 | + // Parse the request URL for matching |
| 219 | + const requestUrl = new URL(req.url); |
| 220 | + |
| 221 | + // Iterate through all registered routes |
| 222 | + for (const route of this.routes) { |
| 223 | + // Check if HTTP method matches (e.g., GET) |
| 224 | + if(req.method === route.method) { |
| 225 | + // Create a URLPattern for the route's path |
| 226 | + const url = new URLPattern({pathname: route.path}) |
| 227 | + |
| 228 | + // Test if the request URL matches the route pattern |
| 229 | + if(url.test(requestUrl)) { |
| 230 | + // Extract URL parameters if there are any |
| 231 | + const matches = url.exec(requestUrl); |
| 232 | + |
| 233 | + if(matches !== null){ |
| 234 | + req.params = matches.pathname.groups; |
| 235 | + } |
| 236 | + |
| 237 | + // If the route pattern includes a search (query) part, assign search params |
| 238 | + if(url.search !== '') { |
| 239 | + req.searchParams = requestUrl.searchParams; |
| 240 | + } |
| 241 | + |
| 242 | + // Call the registered route handler with the request object |
| 243 | + return route.callback(req) |
| 244 | + } |
| 245 | + } |
| 246 | + // If the method does not match, return a 405 Method Not Allowed response |
| 247 | + else { |
| 248 | + return new Response('Method Not Allowed', {status: 405}) |
| 249 | + } |
| 250 | + } |
| 251 | + |
| 252 | + // If no route matches, return a 404 Not Found response |
| 253 | + return new Response('Not found', {status : 404}) |
| 254 | + } |
83 | 255 |
|
84 | | -🖼️ Screenshots |
85 | | -Landing page preview: |
| 256 | +} |
| 257 | +``` |
| 258 | +[Router.ts][5] |
| 259 | + |
| 260 | +--- |
| 261 | +### `log.json` (sample) |
| 262 | +```ts |
| 263 | +"logs":{ |
| 264 | + [ |
| 265 | + { |
| 266 | + "pathname": "/", |
| 267 | + "timestamp": "2025-06-26T00:04:29.685Z" |
| 268 | + }, |
| 269 | + { |
| 270 | + "pathname": "/", |
| 271 | + "timestamp": "2025-06-26T00:06:01.874Z" |
| 272 | + } |
| 273 | + ] |
| 274 | +} |
| 275 | +``` |
| 276 | +[log.json][3] |
86 | 277 |
|
87 | | -. |
| 296 | +Landing page: |
| 297 | +> Welcome To Deno App |
106 | 298 |
|
107 | | -Add more badges (e.g., build status, code coverage) as needed. |
| 299 | +<sub>_This README is styled for clarity and visual appeal, following best practices for GitHub READMEs._</sub> |
108 | 300 |
|
109 | | -For advanced styling, use HTML blocks sparingly for centering or responsive images, as shown above. |
| 301 | +<!-- |
| 302 | +[1]: deno.json |
| 303 | +[2]: index.html |
| 304 | +[3]: log.json |
| 305 | +[4]: main.ts |
| 306 | +[5]: Router.ts |
| 307 | +[6]: StaticFile.ts |
| 308 | +--> |
110 | 309 |
|
111 | | -GitHub Flavored Markdown supports only limited inline CSS and HTML, but the above layout ensures compatibility and readability across devices. |
|
0 commit comments