|
1 | 1 | # plainstack
|
2 | 2 |
|
3 |
| -plainstack is a web framework for TypeScript that allows you to build production-ready apps in a single file. |
| 3 | +Plainstack is a web framework where you start with a single-file. It extends [Hono](https://github.com/honojs/hono) with full-stack concerns like database migrations, background jobs, scheduled jobs, and more. |
4 | 4 |
|
5 |
| -## Example App |
| 5 | +## Features |
6 | 6 |
|
7 |
| -```typescript |
8 |
| -import { Hono } from "hono"; |
9 |
| -import { jsxRenderer } from "hono/jsx-renderer"; |
10 |
| -import { logger } from "hono/logger"; |
11 |
| -import { form, store } from "plainstack"; |
12 |
| -import { bunSqlite, secret } from "plainstack/bun"; |
13 |
| -import { session } from "plainstack/session"; |
| 7 | +- Small API in the spirit of [Hono](https://github.com/honojs/hono) |
| 8 | +- Database migrations |
| 9 | +- Automatic CRUD operations and zod schema for entities |
| 10 | +- Fully typed SQL queries |
| 11 | +- Zod-based form validation |
| 12 | +- Authentication helpers that go well with Hono's OAuth providers |
| 13 | +- Cookie-based session management |
| 14 | +- `<Toast />` component for flash messages |
| 15 | +- Background jobs with parallel workers |
| 16 | +- Scheduled jobs to run code at a specific time or interval (cron syntax) |
| 17 | +- JSX client components |
14 | 18 |
|
15 |
| -interface Items { |
16 |
| - content: string; |
17 |
| - createdAt: number; |
18 |
| - id: string; |
19 |
| -} |
| 19 | +## Getting Started |
20 | 20 |
|
21 |
| -interface DB { |
22 |
| - items: Items; |
23 |
| -} |
| 21 | +Clone the [starter](https://github.com/justplainstuff/starter) repo to get a plainstack app with Tailwind, OAuth and more: |
24 | 22 |
|
25 |
| -const { database, migrate } = bunSqlite<DB>(); |
26 |
| - |
27 |
| -await migrate(({ schema }) => { |
28 |
| - return schema |
29 |
| - .createTable("items") |
30 |
| - .addColumn("id", "text", (col) => col.primaryKey().notNull()) |
31 |
| - .addColumn("content", "text", (col) => col.notNull()) |
32 |
| - .addColumn("created_at", "integer", (col) => col.notNull()) |
33 |
| - .execute(); |
34 |
| -}); |
35 |
| - |
36 |
| -const entities = await store(database); |
| 23 | +```bash |
| 24 | +git clone [email protected]:justplainstuff/starter.git |
| 25 | +``` |
37 | 26 |
|
38 |
| -const app = new Hono(); |
| 27 | +```bash |
| 28 | +bun dev |
| 29 | +``` |
39 | 30 |
|
40 |
| -app.use(logger()); |
41 |
| -app.use(session({ encryptionKey: await secret() })); |
| 31 | +or install `plainstack` manually: |
42 | 32 |
|
43 |
| -app.get( |
44 |
| - "*", |
45 |
| - jsxRenderer(({ children }) => { |
46 |
| - return ( |
47 |
| - <html lang="en"> |
48 |
| - <head> |
49 |
| - <meta charset="utf-8" /> |
50 |
| - <meta name="viewport" content="width=device-width, initial-scale=1" /> |
51 |
| - <meta name="color-scheme" content="light dark" /> |
52 |
| - <link |
53 |
| - rel="stylesheet" |
54 |
| - href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" |
55 |
| - /> |
56 |
| - <link |
57 |
| - rel="stylesheet" |
58 |
| - href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css" |
59 |
| - /> |
60 |
| - <title>So many todos</title> |
61 |
| - </head> |
62 |
| - <body> |
63 |
| - <main class="container"> |
64 |
| - <h1>{children}</h1> |
65 |
| - </main> |
66 |
| - </body> |
67 |
| - </html> |
68 |
| - ); |
69 |
| - }) |
70 |
| -); |
| 33 | +```bash |
| 34 | +bun install plainstack |
| 35 | +``` |
71 | 36 |
|
72 |
| -app.get("/", async (c) => { |
73 |
| - const info = c.var.session.get("info"); |
74 |
| - const items = await entities("items").all(); |
75 |
| - return c.render( |
76 |
| - <div> |
77 |
| - <h1>Todo App</h1> |
78 |
| - {info && <article>{info}</article>} |
79 |
| - <ul class="items-list"> |
80 |
| - {items.map((item) => ( |
81 |
| - <li key={item.id}> |
82 |
| - <div class="grid"> |
83 |
| - {item.content}{" "} |
84 |
| - <form |
85 |
| - style={{ display: "inline" }} |
86 |
| - method="post" |
87 |
| - action={`/delete/${item.id}`} |
88 |
| - > |
89 |
| - <button type="submit">Delete</button> |
90 |
| - </form> |
91 |
| - </div> |
92 |
| - </li> |
93 |
| - ))} |
94 |
| - </ul> |
95 |
| - <form method="post" action="/add"> |
96 |
| - <input |
97 |
| - type="text" |
98 |
| - name="content" |
99 |
| - placeholder="Enter todo item" |
100 |
| - required |
101 |
| - /> |
102 |
| - <button type="submit">Add</button> |
103 |
| - </form> |
104 |
| - </div> |
105 |
| - ); |
106 |
| -}); |
| 37 | +## Documentation [WIP] |
107 | 38 |
|
108 |
| -app.post("/add", form(entities("items").zod), async (c) => { |
109 |
| - const submission = c.req.valid("form"); |
110 |
| - const data = submission.value; |
111 |
| - await entities("items").create(data); |
112 |
| - c.var.session.flash("info", "Item added"); |
113 |
| - return c.redirect("/"); |
114 |
| -}); |
| 39 | +- Database migrations |
| 40 | +- Create entity |
| 41 | +- Update entity |
| 42 | +- Query entity |
| 43 | +- SQL queries |
| 44 | +- Form validation |
| 45 | +- Sessions |
| 46 | +- Authentication |
| 47 | +- Background jobs |
| 48 | +- Scheduled jobs |
| 49 | +- `<Toast />` |
| 50 | +- Client Components |
115 | 51 |
|
116 |
| -app.post("/delete/:id", async (c) => { |
117 |
| - await entities("items").delete(c.req.param("id")); |
118 |
| - c.var.session.flash("info", "Item deleted"); |
119 |
| - return c.redirect("/"); |
120 |
| -}); |
| 52 | +## Examples |
121 | 53 |
|
122 |
| -export default app; |
123 |
| -``` |
| 54 | +Check out the [full list of examples](https://github.com/justplainstuff/plainstack/tree/main/example). |
0 commit comments