A Django-inspired full-stack framework for Deno, written in TypeScript.
Alexi brings Django's developer-friendly patterns to the Deno ecosystem: ORM with multiple backends, REST framework, admin panel, and project scaffolding—all with first-class TypeScript support.
Create a new full-stack project in seconds:
deno run -A jsr:@alexi/create my-project
cd my-project
deno task devThis scaffolds a full-stack blog application (inspired by the Django tutorial) with:
- Server — SSR + REST API on
http://localhost:8000 - Service Worker — same templates rendered in the browser via offline-capable SW
- Admin panel — auto-generated at
/admin/ - Desktop — native window via
deno task webui - Mobile — iOS/Android via
deno task mobile:ios/deno task mobile:android
| Feature | Description |
|---|---|
| ORM | Django-style models, QuerySets, and Managers with SQLite, IndexedDB, and REST backends |
| REST Framework | Serializers, ViewSets, Routers, and filter backends |
| Admin Panel | Auto-generated admin interface for your models |
| Project Scaffolding | @alexi/create generates full-stack projects instantly |
| URL Routing | Django-style path(), include(), and URL patterns |
| Authentication | JWT-based auth with decorators |
| Desktop Apps | WebUI integration for native-like desktop applications |
| Mobile Apps | Capacitor integration for iOS/Android |
deno run -A jsr:@alexi/create my-projectdeno add jsr:@alexi/db jsr:@alexi/restframework jsr:@alexi/coreOr import directly:
import { CharField, Manager, Model } from "jsr:@alexi/db";
import { ModelSerializer, ModelViewSet } from "jsr:@alexi/restframework";// models.ts
import { AutoField, BooleanField, CharField, Manager, Model } from "@alexi/db";
export class TodoModel extends Model {
id = new AutoField({ primaryKey: true });
title = new CharField({ maxLength: 200 });
completed = new BooleanField({ default: false });
static objects = new Manager(TodoModel);
static meta = {
dbTable: "todos",
};
}// serializers.ts
import { ModelSerializer } from "@alexi/restframework";
import { TodoModel } from "./models.ts";
export class TodoSerializer extends ModelSerializer {
static override Meta = {
model: TodoModel,
fields: ["id", "title", "completed"],
readOnlyFields: ["id"],
};
}// viewsets.ts
import { ModelViewSet, QueryParamFilterBackend } from "@alexi/restframework";
import { TodoModel } from "./models.ts";
import { TodoSerializer } from "./serializers.ts";
export class TodoViewSet extends ModelViewSet {
model = TodoModel;
serializer_class = TodoSerializer;
// Enable query parameter filtering
filterBackends = [new QueryParamFilterBackend()];
filtersetFields = ["id", "completed"];
}// urls.ts
import { DefaultRouter } from "@alexi/restframework";
import { TodoViewSet } from "./viewsets.ts";
const router = new DefaultRouter();
router.register("todos", TodoViewSet);
export const urlpatterns = router.urls;// settings.ts
import { DenoKVBackend } from "@alexi/db/backends/denokv";
import { DbConfig } from "@alexi/db";
import { TodoAppConfig } from "./web.ts";
export const INSTALLED_APPS = [DbConfig, TodoAppConfig];
export const ROOT_URLCONF = () => import("./urls.ts");
export const DATABASES = {
default: new DenoKVBackend({ name: "todos", path: "./data/todos.db" }),
};// http.ts
import { getHttpApplication } from "@alexi/core";
export default await getHttpApplication();deno serve -A --unstable-kv http.tsNow you have a full REST API:
GET /api/todos/ # List all todos
POST /api/todos/ # Create a todo
GET /api/todos/:id/ # Get a todo
PUT /api/todos/:id/ # Update a todo
DELETE /api/todos/:id/ # Delete a todo
GET /api/todos/?completed=true # Filter by completed
| Module | Description |
|---|---|
@alexi/create |
Project scaffolding CLI |
@alexi/db |
ORM with DenoKV, SQLite, IndexedDB, and REST backends |
@alexi/restframework |
REST API framework (Serializers, ViewSets, Routers, Filters) |
@alexi/core |
HTTP application, management commands, and utilities |
@alexi/admin |
Auto-generated admin panel |
@alexi/auth |
JWT authentication |
@alexi/middleware |
CORS, logging, and error handling middleware |
@alexi/urls |
URL routing utilities |
@alexi/views |
Template engine and class-based views |
@alexi/storage |
File storage backends |
@alexi/staticfiles |
Static file handling and bundling |
@alexi/webui |
Desktop app support via WebUI |
@alexi/capacitor |
Mobile app support via Capacitor |
@alexi/types |
Shared TypeScript type definitions |
// Get all todos
const todos = await TodoModel.objects.all().fetch();
// Filter
const completed = await TodoModel.objects
.filter({ completed: true })
.fetch();
// Chained filters with lookups
const recent = await TodoModel.objects
.filter({ title__contains: "important" })
.orderBy("-createdAt")
.limit(10)
.fetch();
// Get single object
const todo = await TodoModel.objects.get({ id: 1 });
// Create
const newTodo = await TodoModel.objects.create({
title: "Learn Alexi",
completed: false,
});
// Update
todo.completed.set(true);
await todo.save();
// Delete
await todo.delete();// settings.ts
import { DenoKVBackend } from "@alexi/db/backends/denokv";
import { RestBackend } from "@alexi/db/backends/rest";
export const DATABASES = {
default: new DenoKVBackend({ name: "myapp", path: "./data/myapp.db" }),
api: new RestBackend({ apiUrl: "https://api.example.com/api" }),
};
// Use specific backend
const remote = await TodoModel.objects.using("api").all().fetch();import {
ModelViewSet,
OrderingFilter,
QueryParamFilterBackend,
SearchFilter,
} from "@alexi/restframework";
class ArticleViewSet extends ModelViewSet {
model = ArticleModel;
serializer_class = ArticleSerializer;
filterBackends = [
new QueryParamFilterBackend(),
new OrderingFilter(),
new SearchFilter(),
];
filtersetFields = ["id", "status", "author"];
orderingFields = ["createdAt", "title"];
searchFields = ["title", "body"];
ordering = ["-createdAt"]; // default ordering
}
// Supports:
// GET /api/articles/?status=published
// GET /api/articles/?ordering=-createdAt
// GET /api/articles/?search=typescript
// GET /api/articles/?title__contains=guideimport { action, ModelViewSet } from "@alexi/restframework";
import type { ViewSetContext } from "@alexi/restframework";
class TodoViewSet extends ModelViewSet {
model = TodoModel;
serializer_class = TodoSerializer;
@action({ detail: true, methods: ["POST"] })
async toggle(context: ViewSetContext): Promise<Response> {
const todo = await this.getObject(context);
todo.completed.set(!todo.completed.get());
await todo.save();
return Response.json(await new TodoSerializer({ instance: todo }).data);
}
}
// POST /api/todos/:id/toggle/When you run deno run -A jsr:@alexi/create my-project, you get:
my-project/
├── manage.ts # Management CLI entry point
├── deno.jsonc # Workspace configuration
├── project/
│ ├── settings.ts # Shared settings
│ ├── web.settings.ts # Web server settings
│ ├── ui.settings.ts # UI settings
│ └── desktop.settings.ts # Desktop settings
└── src/
├── my-project-web/ # Backend REST API
│ ├── app.ts
│ ├── models.ts
│ ├── serializers.ts
│ ├── viewsets.ts
│ └── urls.ts
├── my-project-ui/ # Frontend SPA
│ ├── main.ts
│ ├── models.ts
│ ├── views.ts
│ ├── templates/
│ └── components/
└── my-project-desktop/ # Desktop app
└── app.ts
deno run -A --unstable-kv manage.ts runserver --settings ./project/settings.ts
deno run -A --unstable-kv manage.ts makemigrations myapp --settings ./project/settings.ts
deno run -A --unstable-kv manage.ts migrate --settings ./project/settings.ts
deno run -A --unstable-kv manage.ts createsuperuser --settings ./project/settings.ts- Deno 2.0+
--unstable-kvflag for DenoKV backend
See the docs/ directory for detailed guides:
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
MIT License - see LICENSE for details.