diff --git a/README.md b/README.md index 95a637d..97c8f9f 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,18 @@ ## Tech Stacks -- Frontend - React.js, TailwindCSS -- Backend - Express, socket.io, MongoDB, Redis +- Frontend - React.js, TailwindCSS, React-Query +- Backend - Express, socket.io, PostgreSQL, Redis ## Get Started ### Run final build using docker compose -- Spin up the entire stack (redis, postgres, web, server) +- Spin up the entire stack (Redis, PostgreSQL, React.js, Node.js) ```bash -pnpm docker:build +pnpm docker:up ``` +> Make sure ports `6379`,`5432`,`5000` and `3000` are not occupied - Run migrations ```bash pnpm docker:db:migrate @@ -33,6 +34,11 @@ pnpm docker:db:seed ``` - Go to [http://localhost:3000](http://localhost:3000) +- Stop docker containers +```bash +pnpm docker:down +``` + ### Development - Spin up PostgreSQL and Redis @@ -40,7 +46,7 @@ pnpm docker:db:seed docker compose up -d ``` -> Make sure the ports (mongo: 27017, redis: 6379) are open for connection +> Make sure the ports (postgres: 5432, redis: 6379) are open for connection - Install dependencies ```bash @@ -69,7 +75,7 @@ pnpm dev - Run playwright e2e tests ```bash -pnpm --filter e2e test +pnpm e2e:test ``` - Run playwright e2e tests in UI mode ```bash @@ -80,6 +86,7 @@ pnpm --filter e2e test:ui pnpm --filter e2e test:codegen ``` + ## Features Roadmap ### primary goals diff --git a/package.json b/package.json index 511c14a..07f5ab7 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,11 @@ "lint": "pnpm run -r lint", "prepare": "husky install", "commitlint": "commitlint --edit", - "docker:build": "docker compose -f docker-compose.prod.yml up --build -d", + "docker:up": "docker compose -f docker-compose.prod.yml up --build -d", "docker:db:migrate": "docker exec -it mchat-server pnpm migrate:run", "docker:db:seed": "docker exec -it mchat-server pnpm seed", - "docker:up": "pnpm docker:build && pnpm docker:db-migrate" + "docker:down": "docker compose -f docker-compose.prod.yml down", + "e2e:test": "pnpm --filter e2e test" }, "keywords": [ "chat", "realtime", "socket.io", "react", "node.js", "react-query" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17e9207..4975942 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,6 +196,9 @@ importers: prettier-plugin-tailwindcss: specifier: ^0.6.5 version: 0.6.5(prettier@3.3.2) + rollup-plugin-visualizer: + specifier: ^5.12.0 + version: 5.12.0(rollup@4.18.0) tailwindcss: specifier: ^3.4.4 version: 3.4.4(ts-node@10.9.1(@types/node@20.14.5)(typescript@5.4.5)) @@ -1537,6 +1540,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -2050,6 +2057,11 @@ packages: is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -2082,6 +2094,10 @@ packages: resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} engines: {node: '>=8'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -2349,6 +2365,10 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2727,6 +2747,16 @@ packages: engines: {node: '>=14.18'} hasBin: true + rollup-plugin-visualizer@5.12.0: + resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==} + engines: {node: '>=14'} + hasBin: true + peerDependencies: + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rollup: + optional: true + rollup@4.18.0: resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2810,6 +2840,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -4305,6 +4339,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.0.1 + define-lazy-prop@2.0.0: {} + denque@2.1.0: {} depd@2.0.0: {} @@ -4918,6 +4954,8 @@ snapshots: dependencies: hasown: 2.0.2 + is-docker@2.2.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4938,6 +4976,10 @@ snapshots: dependencies: text-extensions: 2.4.0 + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isexe@2.0.0: {} jackspeak@3.4.0: @@ -5149,6 +5191,12 @@ snapshots: dependencies: mimic-fn: 4.0.0 + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5432,6 +5480,15 @@ snapshots: dependencies: glob: 10.4.2 + rollup-plugin-visualizer@5.12.0(rollup@4.18.0): + dependencies: + open: 8.4.2 + picomatch: 2.3.1 + source-map: 0.7.4 + yargs: 17.7.2 + optionalDependencies: + rollup: 4.18.0 + rollup@4.18.0: dependencies: '@types/estree': 1.0.5 @@ -5573,6 +5630,8 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.4: {} + split2@4.2.0: {} standard-as-callback@2.1.0: {} diff --git a/server/src/index.ts b/server/src/index.ts index 46b212d..c212fa8 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -25,14 +25,16 @@ import { const createApp = async () => { if (cluster.isPrimary && config.isProd) { - console.log(`Master ${process.pid} is running`) + console.log(`Primary ${process.pid} is running`) const numCPUs = availableParallelism() const httpServer = createServer() + // setup sticky sessions setupMaster(httpServer, { loadBalancingMethod: 'least-connection' }) + // setup connection between the workers setupPrimary() httpServer.listen(config.port, () => { @@ -40,6 +42,8 @@ const createApp = async () => { }) for (let i = 0; i < numCPUs; i++) { + // Spawn a new worker process. + // This can only be called from the primary process. cluster.fork() } @@ -71,7 +75,10 @@ const createApp = async () => { }) if (config.isProd) { + // use cluster adapter io.adapter(createClusterAdapter()) + + // setup connection with primary process setupWorker(io) } diff --git a/server/src/scripts/seed.ts b/server/src/scripts/seed.ts index 138c87e..ed01144 100644 --- a/server/src/scripts/seed.ts +++ b/server/src/scripts/seed.ts @@ -8,8 +8,8 @@ import { hash } from 'argon2' import 'colors' const USER_PASSWORD = 'bob@123' +const USER_COUNT = 300 -const USER_COUNT = 1000 const GROUP_COUNT_PER_USER = 5 const MEMBER_COUNT_PER_GROUP = 35 const MESSAGE_PER_MEMBER = 5 diff --git a/web/.gitignore b/web/.gitignore index 7ceb59f..b3d372c 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -23,3 +23,4 @@ dist-ssr *.sln *.sw? .env +stats.html diff --git a/web/package.json b/web/package.json index 70bf9c9..9a2b886 100644 --- a/web/package.json +++ b/web/package.json @@ -36,6 +36,7 @@ "postcss": "^8.4.38", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", + "rollup-plugin-visualizer": "^5.12.0", "tailwindcss": "^3.4.4", "typescript": "^5.2.2", "vite": "^5.2.0" diff --git a/web/vite.config.ts b/web/vite.config.ts index 88d1b54..06a86a7 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,10 +1,11 @@ import react from '@vitejs/plugin-react-swc' import path from 'path' +import { visualizer } from 'rollup-plugin-visualizer' import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), visualizer({ gzipSize: true, brotliSize: true })], server: { port: 3000, },