commit d91eb82a4a0bc94c08d144280c94470b18717503 Author: nzambello Date: Mon Aug 4 10:14:50 2025 +0200 initial commit 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 + } +}