From 3a1a106579bb4d432ec35a7414b1686cbc4363c4 Mon Sep 17 00:00:00 2001 From: Antonod1988 <58751274+Antonod1988@users.noreply.github.com> Date: Tue, 26 May 2026 20:55:49 +0300 Subject: [PATCH] Add API response compression --- api/package.json | 2 + api/src/__tests__/compression.test.ts | 17 ++++++ api/src/app.ts | 2 + package-lock.json | 76 +++++++++++++++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 api/src/__tests__/compression.test.ts diff --git a/api/package.json b/api/package.json index 20224a7c..6cf32106 100644 --- a/api/package.json +++ b/api/package.json @@ -29,6 +29,7 @@ "@types/ws": "^8.18.1", "axios": "^1.6.2", "bcryptjs": "^2.4.3", + "compression": "^1.8.1", "cors": "^2.8.5", "dotenv": "^16.6.1", "express": "^4.18.2", @@ -44,6 +45,7 @@ }, "devDependencies": { "@types/bcryptjs": "^2.4.6", + "@types/compression": "^1.8.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", diff --git a/api/src/__tests__/compression.test.ts b/api/src/__tests__/compression.test.ts new file mode 100644 index 00000000..8dc59dbf --- /dev/null +++ b/api/src/__tests__/compression.test.ts @@ -0,0 +1,17 @@ +import request from 'supertest'; +import app, { resetRateLimiters } from '../app'; + +describe('API response compression', () => { + beforeEach(async () => { + await resetRateLimiters(); + }); + + it('compresses large JSON responses when the client accepts gzip', async () => { + const response = await request(app) + .get('/api/openapi.json') + .set('Accept-Encoding', 'gzip'); + + expect(response.status).toBe(200); + expect(response.headers['content-encoding']).toBe('gzip'); + }); +}); diff --git a/api/src/app.ts b/api/src/app.ts index e1b6b614..9e7f259a 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,6 +1,7 @@ import express, { Application, Request, Response, NextFunction } from 'express'; import helmet from 'helmet'; import cors from 'cors'; +import compression from 'compression'; import rateLimit, { MemoryStore } from 'express-rate-limit'; import { config } from './config'; import { bodySizeLimitMiddleware } from './middleware/bodySizeLimit'; @@ -50,6 +51,7 @@ if (config.server.env === 'production') { } app.use(cors()); +app.use(compression()); app.use(express.json({ limit: config.bodySizeLimit.limit })); app.use(express.urlencoded({ extended: true, limit: config.bodySizeLimit.limit })); app.use(sanitizeInput); diff --git a/package-lock.json b/package-lock.json index c62d223f..69787078 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@types/ws": "^8.18.1", "axios": "^1.6.2", "bcryptjs": "^2.4.3", + "compression": "^1.8.1", "cors": "^2.8.5", "dotenv": "^16.6.1", "express": "^4.18.2", @@ -47,6 +48,7 @@ "@stryker-mutator/jest-runner": "^8.7.1", "@stryker-mutator/typescript-checker": "^8.7.1", "@types/bcryptjs": "^2.4.6", + "@types/compression": "^1.8.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.11", @@ -3315,6 +3317,17 @@ "@types/node": "*" } }, + "node_modules/@types/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -4977,6 +4990,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8320,6 +8387,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",