npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fanl

v0.0.2

Published

Fanl is an extremely simple and easy-to-use js server framework driven by the `App Router` file system routing.

Downloads

5

Readme

Fanl 中文

Fanl is an extremely simple and easy-to-use js server framework driven by the App Router file system routing.

Compatible with various js runtimes, such as Bun and Node.

Installation

bun add fanl
# or
npm i fanl
# or
pnpm add fanl

Usage

Use in Bun

Create the app directory

app
└── hello
    └── GET.ts

Edit GET.ts

export default (request: Request) => {
  return new Response("hello");
};

Create main.ts in the root directory

import { serve } from "bun";
import { createFanlFetch } from "fanl";

const fanlFetch = await createFanlFetch("./app");

serve({
  fetch: fanlFetch,
  port: 8080,
});

Use in Node

Step 1: Change *.ts to *.mjs in the above Bun code

Step 2: Import the Node compatibility layer.

Since Node's own HTTP Server API is not compatible with the Web standard Fetch API, a compatibility layer is needed. Fortunately, the Hono framework provides a Node compatibility layer hono/node-server, which is great.

Install @hono/node-server and modify main.mjs

import { serve } from "@hono/node-server";
import { createFanlFetch } from "fanl";

const fanlFetch = await createFanlFetch("./app");

serve({
  fetch: fanlFetch,
  port: 8080,
});

Run

bun run main.ts
# or
node main.mjs

Visit http://localhost:8080/hello

App Router File System Routing

The App Router file system routing specification was first designed and popularized by NextJS, and its simple usage and excellent design provided the most direct inspiration for Fanl. Fanl chose to implement it as a route controller for pure server-side frameworks.

Define Routes

Each folder represents a route segment mapped to a URL segment. To create nested routes, you can nest folders within each other.

app
└── dashboard
    └── settings

Corresponding URLs

  • /dashboard
  • /dashboard/settings

Create Endpoints

Create a GET.ts interface file in the corresponding route directory.

app
└── dashboard
    ├── GET.ts
    └── settings
        └── GET.ts

GET.ts

export default (request: Request) => {
  return new Response("hello");
};

Request

Use fetch to request the interface in the browser.

const res = await fetch("/dashboard");
await res.text(); // hello

Dynamic Routes

The App Router file system routing has three types of dynamic route segments

  • [slug]
  • [...slug]
  • [[...slug]]
app
├── roles
│   └── [id]
│       └── [name]
├── posts
│   └── [[...ids]]
└── users
    └── [...ids]

Corresponding URLs, routes, and parameter matching table

| URL | Route | Params | | ------------ | ------------------- | ---------------------------- | | /roles/a/b | roles/[id]/[name] | { "id": "a", "name": "b" } | | /roles/a | roles/[id] | { "id": "a" } | | /users/a/b | users/[...ids] | { "ids": ["a", "b"] } | | /users/a | users/[...ids] | { "ids": ["a"] } | | /users | users | {} | | /posts/a/b | posts/[[...ids]] | { "ids": ["a", "b"] } | | /posts/a | posts/[[...ids]] | { "ids": ["a"] } | | /posts | posts/[[...ids]] | {"ids": []} |

Get Params in the interface

import { useContext, useParams } from "fanl";

export default (request: Request) => {
  const ctx = useContext(); // ctx.params;
  const params = useParams<T>(); // equivalent to useContext().params
  return new Response("hello");
};

Wildcard Routes

[[...ids]] and [...ids] can capture the remaining route segments and can only be placed at the deepest level of the directory.

The difference between the two is that the former is optional, and the latter is required. An optional segment can match exhausted URL segments, as seen in the matching routes for the URLs /user and /posts in the table above.

Directory Structure

Interfaces

File name: %METHOD%.ts

The implementation file for the interface, which needs to export a default processing function, where %METHOD% is all http protocol methods.

export default (request: Request) => {
  // request.method -> "get" "post" ...
  return new Response("hello");
};

Composite Interfaces

File name: handler.ts

When a route has multiple interface implementations, all methods can be placed in the handler, and the handler supports fallback processing.

export const GET = () => {
  return new Response("GET");
};

export const POST = () => {
  return new Response("POST");
};

// Fallback
export default () => {
  return new Response("ALL");
};

Priority

When the interface file and the composite interface define the same interface method, the interface file takes precedence. If neither is defined, the fallback file in the composite interface is used.

| Object | Priority | | ----------------------- | ------ | | Interface file | High | | Interface exported by composite interface file | Medium | | Default export of composite interface file | Low |

Middleware

The implementation of middleware is similar to koa, using the onion ring model.

app
├── middleware.ts #1
└── roles
    ├── middleware.ts #2
    └── [id]
        ├── GET.ts
        └── middleware.ts #3

Assuming the above middleware is a simple log middleware, as follows:

export default (
  request: Request,
  next: (req: Request) => Promise<Response>
) => {
  console.log("request #x");
  const res = await next(request);
  console.log("response  #x");
  return res;
};

Execution order of middleware for the request /roles/a

request #1
  request #2
    request #3
      get
    response #3
  response #2
response #1

When a route has multiple middleware, you can use the compose method to combine them.

import { compose } from "fanl";

export default compose(middleware1, middleware2, middleware3);