r/bun

PeekM2: A real-time dashboard/viewer for your PM2 processes
▲ 31 r/bun+3 crossposts

PeekM2: A real-time dashboard/viewer for your PM2 processes

Note: I'm just a beginner, don't bash too hard on me :/. AI wasn't used to write this post at all and I'd love if you'd take a minute and check the project out!

AI was used to lend me a hand me during the dev of the project, but it wasn't used extensively and most of the UI is just shad/cn, it was used mostly to help me learn Svelte as it's my first time using it :P

Also, for anyone that isn't inside the JS/TS ecosystem or just don't know what PM2 is, it's just a process manager, allows you to control all of your Node/Bun/Deno processes (or even other interpreters) and auto-start them on boot. Find more about it here: https://pm2.keymetrics.io/

Project Repo: https://github.com/AngelCMHxD/PeekM2

Live demo: https://peekm2.angelcmh.com/

---

I've been looking for PM2 dashboards for quite a while and I've been able to find quite a lot of options, however, none of them were quite what I was looking for.

The official one has a LOT of features, though mostly ones that I don't need, and the only plan with a fixed/public pricing is $39 a MONTH, which is just too much for what I was looking for, and especially for hobby projects.

And for the open-source ones, some of them were too complex to setup, like I don't want to setup a whole PostgreSQL instance just for a PM2 dashboard. And others were over-complicated, I don't really need an user management feature other than just an admin/master password, if I didn't have a dashboard I'd have to give the other maintainers access to the whole daemon anyways. (Though right now you can set up multiple instances, so you can handle access that way too)

So, for mine I built a historical CPU/RAM usage history chart, logs, basic controls to restart/stop/delete processes, and a small uptime chart (that probably needs to be reworked because it only checks every 5 mins, and for it to be reliable it should check WAY more frequently)

Now, I do have other features in mind that I could implement and have them in mind, like these:

  • Discord (or other) webhooks to notify about process changes or downtime.
  • Fix the uptime thing I mentioned before.
  • Add a way to check historical data from other than the last 24h.
  • Related to the one above, limit the amount of historical data stored, as it's currently uncapped and we only use the last 24h, so keep only what's needed
  • Maybe add a way to see the process env variables?

This list is not exhaustive though, and I'm quite open for feedback, even if it's the user management thing I mentioned before that I didn't need, I'll just find a way to make it unobtrusive for anyone that may not want it.

The main point of the project is just keeping it simple for anyone that doesn't want that much, but I could add other features as long as they don't impact the simplicity for anyone that don't want them. Just keeping the "barrier of entry" as low as possible.

I've also deployed a "demo" instance so you can see how it currently looks like, and I've said before, I'm open to feedback!

If you like the idea, please star the repo ;D! It's my first time doing a project and posting it on things like Reddit, I'm just a beginner at these things

Edit: Fixed the formatting... so sad that it didn't worked at first :/

2nd Edit: Also, I'd love to promote Hack Club! It's an amazing non-profit dedicated to incentivize programming for teenagers (13-18 inclusive) and overall just doing cool projects while getting rewards. This project was made/submitted to one of their programs as I'm a teenager myself :D

u/Anglotx — 2 days ago
▲ 5 r/bun+1 crossposts

I built an HTML-first web framework on Bun — Stoneware

>I’ve been building Stoneware, a Bun-native web framework with a simple idea:

HTML is the default. JavaScript is opt-in.

It focuses on:

  • Server-side rendering
  • Islands for interactive components
  • Signals
  • Static export
  • Secure-by-default rendering
  • Bun-native tooling

GitHub: stoneware-core
Docs: Stoneware Docs

u/Whole_Membership_135 — 2 days ago
▲ 13 r/bun

Bun + Elysia is reliable?

I want to build an MVP and I'm planning to use bun + elysia instead of nest.js.

The question is, can I trust this technology? Will it be stable in the next 5-10 years and handle a platform with a few thousand users?

reddit.com
u/Such-Dog9590 — 2 days ago
▲ 3 r/bun+1 crossposts

Realtime on Bun with end-to-end types: a whole chat (rooms, auth, history) in one file — no event-name strings, no generated types (I'm the author)

I'm the author of Point0, a fullstack TypeScript framework on Bun, and I just shipped its realtime layer. Sharing it here because the whole thing rides on Bun: `Bun.serve`'s WebSocket server underneath, and Bun's built-in Redis client is the one-line option when you run more than one process.

The idea: instead of a second stack next to your app (event-name strings, `any` payloads, rooms as string concatenation, your own bookkeeping of users and sockets), realtime is four more declarations of the same kind the framework already uses for pages, queries and mutations. One WebSocket per client, everything else rides it.

- a **channel** is the connection, and its connector turns the HTTP handshake into the connection's identity, stored server-side
- a **space** is a family of rooms of one shape; a room is an object, not a string, and it's the pub/sub address
- a **server handler** is client → server, a **client handler** is server → client, both typed by their schemas

Here's a whole chat: rooms per conversation, auth, persisted history, live updates. One file, and the compiler strips the server half out of the client bundle and the client half out of the server one.

// the channel: one connection per client. The connector runs on a normal HTTP
// request (cookies, headers, middleware), so your existing auth just works
export const appChannel = root.lets
  .channel()
  .connector(async ({ request }) => {
    const user = await getUserFromRequest(request) // your app's auth, unchanged
    // whatever you return IS this connection's identity: server-side, never sent
    // to the client, readable in every callback below. No type declared anywhere
    return user
      ? { authorized: true as const, id: user.id }
      : { authorized: false as const, id: null }
  })
  .channel()


// a space: a family of rooms of one shape. Here, one room per chat
export const chatSpace = appChannel.lets
  .space<{ chatId: string }>() // the room shape, declared like component props
  .input(z.object({ chatId: z.string() })) // what the client passes to join
  .joiner(({ input, identity }) => {
    // entering the room IS the read gate, and it runs on the server
    if (!identity.authorized) throw new AppError('Sign in first', { status: 401 })
    return { chatId: input.chatId } // type-checked against the room shape above
  })
  .space()


// client → server: persist, then fan out to the room
export const messageSendHandler = chatSpace.lets
  .serverHandler()
  .clientSend(z.object({ text: z.string().min(1).max(1000) }))
  .serverReply(async ({ input, identity, room }) => {
    if (!identity.authorized) throw new AppError('Sign in first', { status: 401 })
    const message = await prisma.message.create({
      data: { text: input.text, chatId: room.chatId, authorId: identity.id },
    })
    // one publish into the room's topic — not a walk over connections
    void messageAddedHandler.sendToClient(message, { room })
    return message // what the sender gets back from its own send
  })
  .serverHandler()


// server → client
export const messageAddedHandler = chatSpace.lets
  .clientHandler()
  .serverSend(messageSchema)
  .clientHandler()


// ...and the component, in the same file
const Chat = ({ chatId }: { chatId: string }) => {
  const membership = chatSpace.useMembership({ chatId }) // in the room while mounted
  const { data } = messagesQuery.useQuery({ chatId })    // history: an ordinary HTTP query
  const [text, setText] = useState('')


  // `message` is typed by .serverSend, `room` by the space's generic
  messageAddedHandler(membership).useOnMessageFromServer(({ message }) => {
    messagesQuery.setQueryData({ chatId }, (old) => ({
      messages: [...(old?.messages ?? []), message],
    }))
  })


  return (
    <>
      <ul>{data?.messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
      <form onSubmit={(e) => {
        e.preventDefault()
        setText('')
        void messageSendHandler(membership).sendToServer({ text })
      }}>
        <input value={text} onChange={(e) => setText(e.target.value)} />
      </form>
    </>
  )
}

Nothing is generated and nothing is annotated: the identity type comes from the connector's return, the room type from the space's generic, the payload types from the schemas, and they reach every callback on both sides.

A few design decisions worth stating plainly, because they're the part people argue with:

- **A push is a signal, not storage.** Delivery is at-most-once by default: the truth lives in a query, the push only says it went stale. There's an opt-in resume buffer for short drops, and it tells the client honestly whether the gap was covered, so the catch-up refetch is one condition.
- **A room is an object, and its serialization is its address.** `{ members: [a, b].sort() }` is a DM room. No prefix conventions to typo.
- **Two ways in.** `.joiner` is the client asking in and able to leave. `.enroller` is the server putting a connection into a room at connect time, which the client cannot leave — that's what makes a personal push room something you can rely on.
- **Multi-process is config, not rewriting.** Default is process memory; a Redis URL, Postgres LISTEN/NOTIFY, or five functions of your own turn it into a backplane.

More examples (a live board, DMs, and a site where every request travels the socket instead of HTTP): https://1gr14.dev/blog/point0-socket

Socket docs: https://1gr14.dev/point0/latest/socket
The example app: https://github.com/1gr14/point0/tree/main/examples/socket
The framework: https://github.com/1gr14/point0

It's the newest part of the framework and I say so in the docs: the API design has settled, what's underneath still needs a refactor. Happy to take the uncomfortable questions — especially from anyone who has run Socket.IO at scale and sees where this breaks.zc

reddit.com
u/1gr14 — 8 days ago
▲ 1 r/bun+1 crossposts

Zero dependencies: what we deleted and upgraded

UQL is the lightest ORM of all, and here is what it deleted (and upgraded as well) for the edged and mobile.

uql-orm.dev
u/sonemonu — 10 days ago
▲ 0 r/bun

Bun's Android build isn't just another platform target to me. It's an escape hatch.

First of all, thank you so much to the Bun team for supporting Android.

It's hard to realize just how much this means to Android users.

Traditionally, mobile platforms have been treated as inferior computing environments, unable to run many of the tools we take for granted on desktop Linux. Termux completely changed that story by giving Android a native shell environment.

But that freedom comes at a price.

Android uses Bionic libc rather than glibc, which means Linux tools generally need to be rebuilt specifically for the Android environment. Much like the situation with musl-based distributions, you can't simply assume that an arbitrary Linux binary, even if built for arm64, will run.

The enormous package ecosystem that makes Termux so powerful is maintained largely by volunteer developers, and I've always had this fear in the back of my mind: what if one day that package ecosystem is no longer maintained?

So after years of using Node.js and Bun, I've gradually built my own collection of basic tools in JavaScript. Part of the motivation was simple: I wanted to make sure that no matter what happens to the surrounding ecosystem, I can still have a useful shell environment on Android.

And this is where Bun's Android build becomes much more than just another platform target to me.

It's the seed that lets me rebuild everything else.

As long as I can use Android's ProcessBuilder to spawn a Bun binary, I have JavaScript. I have my own tools. I have bunx. I have the whole npm ecosystem. I can start servers, build terminal interfaces, and gradually bootstrap the rest of my environment.

I no longer need to depend on someone else rebuilding every tool I need against Bionic libc.

Platforms can change. Package repositories can disappear. Maintainers can move on.

But as long as I can still ignite that one Bun binary on Android, I have an escape hatch back to a real computing environment.

On Linux, we have Linux From Scratch.

Now on Android, I guess we have Shell From Scratch.

Once Bun is alive, the next step is surprisingly simple: Bun spawns my jsgotty, which exposes a real PTY-backed terminal through a local web server. I point an Android WebView at it, and suddenly:

we have the shell back.

No terminal emulator to depend on. No existing shell environment required. Just an Android app, a Bun binary, and JavaScript bootstrapping its own terminal.

And once I have a shell, things start getting interesting.

Because a shell is enough to launch proot.

And with proot I can finally embrace the glorious Debian and Alpine repositories with apt and apk.

Thousands upon thousands of packages, maintained for standard Linux environments, are suddenly within reach.

The shell has grown into real Linux.

reddit.com
u/LeftAd1220 — 14 days ago