⚠️ AlabJS is under active development and not yet production-ready. APIs may change before v1.0. Feel free to explore, contribute, or star the repo.
Skip to content

Routing

AlabJS uses a file-system router. Every page.tsx in the app/ directory becomes a route. No config, no manual registration.

Basic Routes

FileRoute
app/page.tsx/
app/about/page.tsx/about
app/posts/page.tsx/posts
app/posts/[id]/page.tsx/posts/:id
app/posts/[id]/edit/page.tsx/posts/:id/edit
app/[...slug]/page.tsx/anything/deeply/nested

Layouts

Place a layout.tsx in any directory. AlabJS automatically wraps child pages with the nearest parent layouts, outermost first.

tsx
// app/layout.tsx — wraps every page
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className="bg-white text-gray-900">
        {children}
      </body>
    </html>
  );
}
tsx
// app/dashboard/layout.tsx — wraps /dashboard/* only
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1 p-8">{children}</main>
    </div>
  );
}

Dynamic Segments

Use [param] folder names for dynamic routes. Params are passed to the page as props.

tsx
// app/posts/[id]/page.tsx
export default function PostPage({ params }: { params: { id: string } }) {
  return <h1>Post {params.id}</h1>;
}

Loading UI

Create loading.tsx in any directory to show a Suspense fallback while data loads.

tsx
// app/posts/[id]/loading.tsx
export default function Loading() {
  return <div className="animate-pulse h-8 bg-gray-200 rounded" />;
}

Error Boundaries

Create error.tsx to catch render errors in a subtree.

tsx
// app/posts/[id]/error.tsx
export default function PostError({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div className="p-8 text-center">
      <p className="text-red-600">{error.message}</p>
      <button onClick={reset} className="mt-4 btn">Try again</button>
    </div>
  );
}

Not-Found Page

tsx
// app/not-found.tsx
export default function NotFound() {
  return (
    <div className="p-8 text-center">
      <h1 className="text-4xl font-bold">404</h1>
      <p className="mt-2 text-gray-600">Page not found.</p>
    </div>
  );
}

API Routes

Create route.ts in any directory to expose HTTP endpoints.

ts
// app/api/posts/route.ts
export async function GET(req: Request): Promise<Response> {
  const posts = await db.posts.findAll();
  return Response.json(posts);
}

export async function POST(req: Request): Promise<Response> {
  const body = await req.json();
  const post = await db.posts.create(body);
  return Response.json(post, { status: 201 });
}

Supported methods: GET, POST, PUT, PATCH, DELETE, HEAD.

Middleware

Create middleware.ts at the project root to run code before every request.

ts
// middleware.ts
import { redirect, next } from "alabjs/middleware";

export async function middleware(req: Request) {
  const { pathname } = new URL(req.url);

  if (pathname.startsWith("/dashboard")) {
    const session = req.headers.get("cookie")?.includes("session");
    if (!session) return redirect("/login");
  }

  return next();
}

// Optional: restrict to specific paths
export const config = {
  matcher: ["/dashboard/:path*", "/api/:path*"],
};

i18n Routing

ts
// i18n.ts
import { createI18nConfig } from "alabjs/i18n";

export const i18n = createI18nConfig({
  locales: ["en", "fil", "es"],
  defaultLocale: "en",
});
ts
// middleware.ts
import { i18n } from "./i18n.js";
import { redirect, next } from "alabjs/middleware";

export async function middleware(req: Request) {
  const { pathname } = new URL(req.url);
  if (!i18n.hasLocalePrefix(pathname)) {
    return redirect(`/${i18n.detectLocale(req)}${pathname}`);
  }
  return next();
}
tsx
// app/[locale]/layout.tsx
import { LocaleProvider } from "alabjs/i18n";

export default function LocaleLayout({ params, children }) {
  return <LocaleProvider locale={params.locale}>{children}</LocaleProvider>;
}

Released under the MIT License.