Skip to content

Slick is a small, simple, and ultrafast web framework built on Web Standards for Deno.

License

Notifications You must be signed in to change notification settings

8borane8/webtools-slick-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

30 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Welcome to Slick Server!

Slick is a small, simple, and ultrafast web framework built on Web Standards for Deno.

issues-closed Β  license Β  stars Β  forks


✨ Features

  • Fast and lightweight - Built for performance with minimal overhead
  • Server-side rendering - Built-in SSR with Preact for optimal SEO
  • Automatic asset optimization - CSS, JS, and TS files are minified and transpiled automatically
  • SPA capabilities - Seamless client-side navigation with @webtools/slick-client
  • Type-safe - Full TypeScript support with type checking
  • Static file serving - Efficient static asset delivery
  • Easy configuration - Simple setup with sensible defaults
  • Web Standards - Built on modern web standards and Deno

πŸ“¦ Installation

Step 1: Install the Package

Install Slick Server using Deno's package manager:

deno add jsr:@webtools/slick-server

Step 2: Configure deno.json

Create or update your deno.json file with the required configuration:

{
	"imports": {
		"@webtools/slick-server": "jsr:@webtools/slick-server@^0.4.0"
	},
	"compilerOptions": {
		"jsxImportSource": "preact",
		"jsx": "react-jsx"
	}
}

Important: The compilerOptions are required for JSX to work with Preact. Without them, you'll get compilation errors when using JSX syntax in your pages and templates.

Optional: Add Client Package

If you're using SPA mode (client: true), also install the client package:

deno add jsr:@webtools/slick-client

And add it to your deno.json:

{
	"imports": {
		"@webtools/slick-server": "jsr:@webtools/slick-server@^0.4.0",
		"@webtools/slick-client": "jsr:@webtools/slick-client@^0.2.0"
	},
	"compilerOptions": {
		"jsxImportSource": "preact",
		"jsx": "react-jsx"
	}
}

πŸš€ Quick Start

1. Create Project Structure

my-slick-app/
β”œβ”€β”€ pages/
β”‚   └── index.tsx
β”œβ”€β”€ templates/
β”‚   └── app.tsx
β”œβ”€β”€ static/
β”‚   β”œβ”€β”€ styles/
β”‚   └── scripts/
└── server.ts

2. Setup Server (server.ts)

import { Slick } from "@webtools/slick-server";

const slick = new Slick(import.meta.dirname!, {
	env: {
		API_URL: Deno.env.get("API_URL") || "http://localhost:3000",
	},
	port: 5000,
	lang: "en",
	r404: "/",
	client: true, // Enable SPA mode
});

await slick.start();

3. Create a Template (templates/app.tsx)

import * as Slick from "@webtools/slick-server";

export default {
	name: "app",
	favicon: "/favicon.ico",

	styles: [
		"/styles/reset.css",
		"/styles/app.css",
	],
	scripts: [
		"/scripts/app.ts",
	],

	head: (
		<>
			<meta name="description" content="My Slick App" />
		</>
	),
	body: (
		<div id="app">
			{/* Page content will be injected here */}
		</div>
	),

	onrequest: null,
} satisfies Slick.Template;

4. Create a Page (pages/index.tsx)

import * as Slick from "@webtools/slick-server";

export default {
	url: "/",
	template: "app",

	title: "Home - My App",

	styles: [
		"/styles/pages/index.css",
	],
	scripts: [
		"/scripts/pages/index.ts",
	],

	head: (
		<>
			<meta property="og:title" content="Home" />
		</>
	),
	body: (
		<>
			<h1>Welcome to Slick Server!</h1>
			<p>This is your home page.</p>
		</>
	),

	onpost: null,
	onrequest: null,
} satisfies Slick.Page;

5. Run Your Server

deno run --allow-net --allow-read server.ts

πŸ“ Project Structure

Slick Server requires a specific directory structure:

project-root/
β”œβ”€β”€ pages/          # Page definitions (required)
β”‚   β”œβ”€β”€ index.tsx
β”‚   └── about.tsx
β”œβ”€β”€ templates/      # Template definitions (required)
β”‚   β”œβ”€β”€ app.tsx
β”‚   └── admin.tsx
└── static/         # Static assets (required)
    β”œβ”€β”€ styles/
    β”‚   β”œβ”€β”€ reset.css
    β”‚   └── app.css
    β”œβ”€β”€ scripts/
    β”‚   └── app.ts
    └── assets/
        └── favicon.ico

Directory Descriptions

  • pages/ - Contains page components. Each file must export a default Page object.
  • templates/ - Contains template structures. Each file must export a default Template object.
  • static/ - All files are served as static assets. CSS, JS, and TS files are automatically minified and transpiled.

βš™οΈ Configuration

The Slick constructor accepts a configuration object:

interface Config {
	env: Record<string, string>; // Environment variables for static files
	port: number; // Server port (default: 5000)
	lang: string; // HTML lang attribute (default: "en")
	r404: string; // 404 redirect URL (default: "/")
	client: boolean; // Enable SPA mode (default: false)
}

Configuration Options

Option Type Default Description
env Record<string, string> {} Environment variables available in static files via esbuild.define
port number 5000 Port number for the HTTP server
lang string "en" Language code for the HTML lang attribute
r404 string "/" URL to redirect to when a page is not found
client boolean false Enable SPA mode with @webtools/slick-client

πŸ“„ Pages

A Page object defines a route and its content:

interface Page {
	url: string; // Route URL (must start with /)
	template: string; // Template name to use
	title: string; // Page title
	styles: string[]; // CSS file paths
	scripts: string[]; // JS/TS file paths
	head: Render | null; // Additional head content
	body: Render | null; // Page body content
	onpost: RequestListener | null; // POST request handler
	onrequest: RequestListener | null; // Request middleware (use res.redirect() for redirects)
}

Page Example

import * as Slick from "@webtools/slick-server";
import type { HttpRequest, HttpResponse } from "@webtools/expressapi";

export default {
	url: "/about",
	template: "app",
	title: "About Us",

	styles: ["/styles/pages/about.css"],
	scripts: ["/scripts/pages/about.ts"],

	head: <meta name="description" content="Learn more about us" />,
	body: (
		<>
			<h1>About Us</h1>
			<p>We are a great company!</p>
		</>
	),

	onpost: async (req: HttpRequest, res: HttpResponse) => {
		// Handle POST requests
		return res.json({ success: true });
	},

	onrequest: async (req: HttpRequest, res: HttpResponse) => {
		// Middleware - can redirect or modify request
		if (!req.headers.has("authorization")) {
			return res.redirect("/login");
		}
	},
} satisfies Slick.Page;

Render Type

The head and body properties accept a Render type:

type Render =
	| ((req: HttpRequest) => Promise<preact.VNode> | preact.VNode)
	| preact.VNode;

You can use:

  • Static VNode: <div>Hello</div>
  • Function: (req) => <div>Hello {req.url}</div>
  • Async Function: async (req) => { const data = await fetchData(); return <div>{data}</div>; }

🎨 Templates

A Template object defines the page structure:

interface Template {
	name: string; // Unique template name
	favicon: string; // Favicon path
	styles: string[]; // Global CSS files
	scripts: string[]; // Global JS/TS files
	head: Render | null; // Global head content
	body: Render | null; // Template body (must contain element with id="app")
	onrequest: RequestListener | null; // Global request middleware (use res.redirect() for redirects)
}

Template Example

import * as Slick from "@webtools/slick-server";

export default {
	name: "app",
	favicon: "/assets/favicon.ico",

	styles: [
		"/styles/reset.css",
		"/styles/global.css",
	],
	scripts: [
		"/scripts/global.ts",
	],

	head: (
		<>
			<meta name="theme-color" content="#000000" />
			<link rel="preconnect" href="https://fonts.googleapis.com" />
		</>
	),

	body: (
		<>
			<header>
				<nav>
					<a href="/">Home</a>
					<a href="/about">About</a>
				</nav>
			</header>
			<main id="app">
				{/* Page content injected here */}
			</main>
			<footer>
				<p>&copy; 2024 My App</p>
			</footer>
		</>
	),

	onrequest: null,
} satisfies Slick.Template;

Important: The template's body must contain an element with id="app" where page content will be injected.

πŸ“¦ Static Files

All files in the static/ directory are served directly. Special handling applies to:

CSS Files

  • Automatically minified
  • Served with Content-Type: text/css

JavaScript/TypeScript Files

  • Automatically transpiled (TS β†’ JS)
  • Automatically minified
  • Served as ES modules
  • Environment variables from config.env are available via esbuild.define

Other Files

  • Served as-is (images, fonts, etc.)

Example Static File Structure

static/
β”œβ”€β”€ styles/
β”‚   β”œβ”€β”€ reset.css
β”‚   β”œβ”€β”€ app.css
β”‚   └── pages/
β”‚       └── index.css
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ app.ts
β”‚   └── pages/
β”‚       └── index.ts
└── assets/
    β”œβ”€β”€ favicon.ico
    └── logo.png

Using Environment Variables in Static Files

Environment variables defined in config.env are available in your static TypeScript/JavaScript files:

// static/scripts/app.ts
console.log(API_URL); // Available if defined in config.env

πŸ”„ Client Mode (SPA)

When client: true is enabled, Slick Server integrates with @webtools/slick-client for SPA functionality:

Features

  • Client-side navigation without full page reloads
  • Automatic template and page updates
  • Optimized asset loading

Setup

  1. Enable client mode in your server config:
const slick = new Slick(import.meta.dirname!, {
	client: true,
	// ... other config
});
  1. Install the client package in your static scripts:
// static/scripts/app.ts
import { Slick } from "@webtools/slick-client";

// Client is automatically initialized by the server
// You can add custom navigation handlers here

How It Works

  • The server injects @webtools/slick-client automatically
  • Pages are loaded via AJAX when navigating
  • Only changed parts (template or page) are updated
  • Full HTML is served on initial load for SEO

πŸ”Œ API Reference

Slick Class

Constructor

new Slick(workspace: string, config: Partial<Config>)
  • workspace: Path to your project root (use import.meta.dirname!)
  • config: Partial configuration object

Methods

start(): Promise<void>

Starts the server. This method:

  • Validates the workspace structure
  • Loads all pages and templates
  • Registers routes
  • Starts the HTTP server
await slick.start();

Types

Page

interface Page {
	readonly url: string;
	readonly template: string;
	readonly title: string;
	readonly styles: string[];
	readonly scripts: string[];
	readonly head: Render | null;
	readonly body: Render | null;
	readonly onpost: RequestListener | null;
	readonly onrequest: RequestListener | null;
}

Template

interface Template {
	readonly name: string;
	readonly favicon: string;
	readonly styles: string[];
	readonly scripts: string[];
	readonly head: Render | null;
	readonly body: Render | null;
	readonly onrequest: RequestListener | null;
}

Render

type Render =
	| ((req: HttpRequest) => Promise<preact.VNode> | preact.VNode)
	| preact.VNode;

πŸ’‘ Examples

Dynamic Page Content

export default {
	url: "/products/:id",
	template: "app",
	title: "Product",

	body: async (req: HttpRequest) => {
		const productId = req.url.split("/").pop();
		const product = await fetchProduct(productId);

		return (
			<>
				<h1>{product.name}</h1>
				<p>{product.description}</p>
				<p>Price: ${product.price}</p>
			</>
		);
	},
	// ... other properties
} satisfies Slick.Page;

Authentication Middleware

import type { HttpRequest, HttpResponse } from "@webtools/expressapi";

export default {
	url: "/dashboard",
	template: "app",
	title: "Dashboard",

	onrequest: async (req: HttpRequest, res: HttpResponse) => {
		const token = req.headers.get("authorization");
		if (!token || !await validateToken(token)) {
			return res.redirect("/login"); // Redirect to login
		}
	},
	// ... other properties
} satisfies Slick.Page;

POST Handler

import type { HttpRequest, HttpResponse } from "@webtools/expressapi";

export default {
	url: "/api/contact",
	template: "app",
	title: "Contact",

	onpost: async (req: HttpRequest, res: HttpResponse) => {
		const { name, email, message } = req.body;

		// Process the form submission
		await sendEmail({ name, email, message });

		return res.json({ success: true, message: "Message sent!" });
	},
	// ... other properties
} satisfies Slick.Page;

πŸ“ License

Distributed under the MIT License. See LICENSE for more information.