From d91eb82a4a0bc94c08d144280c94470b18717503 Mon Sep 17 00:00:00 2001 From: nzambello Date: Mon, 4 Aug 2025 10:14:50 +0200 Subject: [PATCH] initial commit --- .../use-bun-instead-of-node-vite-npm-pnpm.mdc | 111 ++++++++++++++++ .dockerignore | 15 +++ .gitignore | 34 +++++ Dockerfile.client | 34 +++++ Dockerfile.server | 34 +++++ README.md | 122 ++++++++++++++++++ bun.lock | 38 ++++++ client.ts | 44 +++++++ docker-compose.yml | 29 +++++ index.ts | 17 +++ package.json | 15 +++ tsconfig.json | 29 +++++ 12 files changed, 522 insertions(+) create mode 100644 .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile.client create mode 100644 Dockerfile.server create mode 100644 README.md create mode 100644 bun.lock create mode 100644 client.ts create mode 100644 docker-compose.yml create mode 100644 index.ts create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 100644 index 0000000..b8100b7 --- /dev/null +++ b/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9b49524 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore +README.md +LICENSE +.vscode +Makefile +helm-charts +.env +.editorconfig +.idea +coverage* \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/Dockerfile.client b/Dockerfile.client new file mode 100644 index 0000000..fd48e48 --- /dev/null +++ b/Dockerfile.client @@ -0,0 +1,34 @@ +# use the official Bun image +# see all versions at https://hub.docker.com/r/oven/bun/tags +FROM oven/bun:1 AS base +WORKDIR /usr/src/app + +# install dependencies into temp directory +# this will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lock /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile + +# install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lock /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +# copy node_modules from temp directory +# then copy all (non-ignored) project files into the image +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . + +ENV NODE_ENV=production + +# copy production dependencies and source code into final image +FROM base AS release +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /usr/src/app/client.ts . +COPY --from=prerelease /usr/src/app/package.json . + +# run the app +USER bun +ENTRYPOINT [ "bun", "run", "client.ts" ] \ No newline at end of file diff --git a/Dockerfile.server b/Dockerfile.server new file mode 100644 index 0000000..c616ed4 --- /dev/null +++ b/Dockerfile.server @@ -0,0 +1,34 @@ +# use the official Bun image +# see all versions at https://hub.docker.com/r/oven/bun/tags +FROM oven/bun:1 AS base +WORKDIR /usr/src/app + +# install dependencies into temp directory +# this will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lock /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile + +# install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lock /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +# copy node_modules from temp directory +# then copy all (non-ignored) project files into the image +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . + +ENV NODE_ENV=production + +# copy production dependencies and source code into final image +FROM base AS release +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /usr/src/app/index.ts . +COPY --from=prerelease /usr/src/app/package.json . + +# run the app +USER bun +ENTRYPOINT [ "bun", "run", "index.ts" ] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b83bd09 --- /dev/null +++ b/README.md @@ -0,0 +1,122 @@ +# NATS Messaging System + +A simple NATS client/server setup for testing message publishing and subscribing using Docker Compose. + +## Architecture + +- **NATS Server**: Message broker running on port 4222 +- **Server (index.ts)**: Subscribes to messages and displays them +- **Client (client.ts)**: Interactive CLI for publishing messages + +## Prerequisites + +- Docker and Docker Compose +- (Optional) Bun runtime for local development + +## Running with Docker Compose (Recommended) + +### 1. Start the NATS server and message subscriber + +```bash +docker-compose up nats server -d +``` + +This starts: + +- NATS message broker in the background +- Server that subscribes to messages and displays them + +### 2. Run the interactive client + +```bash +docker-compose run --rm client +``` + +This opens an interactive prompt where you can: + +- Type messages and press Enter to send them +- Type `quit` or `exit` to stop the client +- Use Ctrl+C to force exit + +### 3. View server logs (optional) + +To see incoming messages in real-time: + +```bash +docker-compose logs -f server +``` + +### 4. Stop everything + +```bash +docker-compose down +``` + +## Example Usage + +1. **Terminal 1**: Start the infrastructure + + ```bash + docker-compose up nats server -d + ``` + +2. **Terminal 2**: Run the client + + ```bash + docker-compose run --rm client + ``` + + You'll see: + + ``` + Connected to NATS at nats://nats:4222 + Enter a message (or 'quit' to exit): Hello World! + Enter a message (or 'quit' to exit): This is a test + Enter a message (or 'quit' to exit): quit + Goodbye! + ``` + +3. **Terminal 1**: Check server logs to see received messages + ```bash + docker-compose logs server + ``` + +## Local Development (without Docker) + +If you prefer to run locally: + +### Install dependencies + +```bash +bun install +``` + +### Start NATS server + +```bash +docker run -p 4222:4222 nats:latest +``` + +### Run the server (in one terminal) + +```bash +bun run index.ts +``` + +### Run the client (in another terminal) + +```bash +bun run client.ts +``` + +## Environment Variables + +- `NATS_SERVER`: NATS server URL (default: `nats://localhost:4222`) + +## Troubleshooting + +- **Client not accepting input**: Make sure you're using `docker-compose run --rm client` instead of `docker-compose up client` +- **Connection refused**: Ensure NATS server is running first +- **Messages not appearing**: Check that the server container is running with `docker-compose ps` + +This project was created using `bun init` in bun v1.2.18. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..f0512a1 --- /dev/null +++ b/bun.lock @@ -0,0 +1,38 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "nats", + "dependencies": { + "nats": "^2.29.3", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="], + + "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], + + "@types/react": ["@types/react@19.1.9", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA=="], + + "bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "nats": ["nats@2.29.3", "", { "dependencies": { "nkeys.js": "1.1.0" } }, "sha512-tOQCRCwC74DgBTk4pWZ9V45sk4d7peoE2njVprMRCBXrhJ5q5cYM7i6W+Uvw2qUrcfOSnuisrX7bEx3b3Wx4QA=="], + + "nkeys.js": ["nkeys.js@1.1.0", "", { "dependencies": { "tweetnacl": "1.0.3" } }, "sha512-tB/a0shZL5UZWSwsoeyqfTszONTt4k2YS0tuQioMOD180+MbombYVgzDUYHlx+gejYK6rgf08n/2Df99WY0Sxg=="], + + "tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="], + + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], + } +} diff --git a/client.ts b/client.ts new file mode 100644 index 0000000..f473104 --- /dev/null +++ b/client.ts @@ -0,0 +1,44 @@ +import { connect } from "nats"; +import readline from "readline"; + +const NATS_SERVER = process.env.NATS_SERVER || "nats://localhost:4222"; + +const nc = await connect({ servers: NATS_SERVER }); +console.log(`Connected to NATS at ${NATS_SERVER}`); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +// Handle Docker container signals gracefully +process.on("SIGINT", () => { + console.log("\nReceived SIGINT, closing connections..."); + rl.close(); + nc.close(); + process.exit(0); +}); + +process.on("SIGTERM", () => { + console.log("\nReceived SIGTERM, closing connections..."); + rl.close(); + nc.close(); + process.exit(0); +}); + +function askForMessage() { + rl.question("Enter a message (or 'quit' to exit): ", (message) => { + if (message.toLowerCase() === "quit" || message.toLowerCase() === "exit") { + console.log("Goodbye!\n"); + rl.close(); + nc.close(); + process.exit(0); + } + + nc.publish("test", message); + + askForMessage(); + }); +} + +askForMessage(); diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..063cac1 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +services: + nats: + # docker run -p 4222:4222 -ti nats:latest + image: nats:latest + container_name: nats + ports: + - 4222:4222 + + server: + build: + context: . + dockerfile: Dockerfile.server + container_name: server + depends_on: + - nats + environment: + - NATS_SERVER=nats://nats:4222 + + client: + build: + context: . + dockerfile: Dockerfile.client + container_name: client + tty: true + stdin_open: true + depends_on: + - nats + environment: + - NATS_SERVER=nats://nats:4222 diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..35171bb --- /dev/null +++ b/index.ts @@ -0,0 +1,17 @@ +import { connect } from "nats"; + +const NATS_SERVER = process.env.NATS_SERVER || "nats://localhost:4222"; + +const nc = await connect({ servers: NATS_SERVER }); + +console.log("Connected to NATS"); + +const sub = nc.subscribe("test"); + +console.log("Subscribed to [test], waiting for messages...\n"); + +for await (const msg of sub) { + console.log( + `Received message [${msg.subject} - ${msg.sid}]: ${msg.data.toString()}` + ); +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cd2bc81 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "nats", + "module": "index.ts", + "type": "module", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "nats": "^2.29.3" + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}