|
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