diff --git a/.dockerignore b/.dockerignore
index 6d68aea..eb11526 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,5 @@
**/.classpath
**/.dockerignore
-**/.env
**/.git
**/.gitignore
**/.project
diff --git a/.env b/.env
index ccd0c0e..5621709 100644
--- a/.env
+++ b/.env
@@ -1,3 +1,5 @@
PROJECT_NAME=mock-api-framework
SERVER_PORT=8000
-USE_API_URL_PREFIX=api
\ No newline at end of file
+USE_API_URL_PREFIX=api
+LOG_REQUESTS=ON
+DELETE_LOGS_ON_SERVER_RESTART=ON
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 620bf21..71e21d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
node_modules
*.log
screenshots/
-videos/
\ No newline at end of file
+videos/
diff --git a/README.md b/README.md
index 20b6a90..7b27460 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ The framework is written in TypeScript and can :
- Serve persisted mock data to the database
- Perform CRUD operations on the local database via a REST endpoint
- Mock API error codes/messages for testing frontend error handling logic
+- Log and store API requests in JSON format and display information on the localhost:8000/logs route
## Set-up
@@ -64,7 +65,7 @@ A list of all endpoints can be viewed on http://localhost:8000/.
The project has been set-up with demo endpoints that can be removed or modified as needed.
-
+
### Useful Commands
@@ -297,6 +298,48 @@ this will return a 500 error code and the JSON response below:
```
+## Logging
+
+API request information and sent data can be logged and stored as JSON in the /src/logs/ folder.
+
+Logs can be viewed at **localhost:8000/logs**.
+
+
+
+### Set-up
+
+To enable logging set the environment variables below in the .env
+
+```js
+LOG_REQUESTS = ON;
+DELETE_LOGS_ON_SERVER_RESTART = ON;
+```
+
+You can choose to refresh the logfile every time the server restarts or persist the data by setting the DELETE_LOGS_ON_SERVER_RESTART variable.
+
+To set up logging for a route, add the following to the api.ts file in the relevant handler, adjusting the request type (GET/POST/PUT/DELETE) and passing data to be logged as required:
+
+```js
+import logger from '../../utilities/logger';
+
+function handler(pathName: string) {
+ return [
+ http.get(`/${pathName}`, ({ request }) => {
+
+ ...
+ logger({
+ data: { <- extracted request body or query params data here -> },
+ pathName,
+ type: 'GET',
+ });
+ ...
+ }),
+ ]
+}
+```
+
+**See the src/api/bikes api.ts file for an example of logging set up.**
+
## Customisation
### Changing api url prefix
diff --git a/cypress/e2e/log-page-spec.cy.ts b/cypress/e2e/log-page-spec.cy.ts
new file mode 100644
index 0000000..bd2a941
--- /dev/null
+++ b/cypress/e2e/log-page-spec.cy.ts
@@ -0,0 +1,41 @@
+describe('logging works expected information', () => {
+ it('log page works', () => {
+ cy.visit('/logs');
+ cy.get('h2').contains('API Requests Made');
+
+ cy.get('h3').contains(
+ 'File can be viewed in /src/logs folder in container or local machine',
+ );
+ cy.get('h5').contains(
+ "LOG_REQUESTS env var must be set to 'ON' to log requests",
+ );
+ });
+
+ it('logging GET request works', () => {
+ cy.request('/api/bikes?type=ducati');
+ cy.visit('/logs');
+ cy.get('.json-container').should('exist');
+ cy.get('.json-container').contains('api/bikes');
+ cy.get('.json-container').contains('ducati');
+ cy.get('.json-container').contains('GET');
+ });
+
+ it('logging POST request works', () => {
+ cy.request('POST', '/api/bikes', {
+ name: 'kawasaki ninja',
+ type: 'kawasaki',
+ year: 2023,
+ color: 'red',
+ price: 20000,
+ });
+ cy.visit('/logs');
+ cy.get('.json-container').should('exist');
+ cy.get('.json-container').contains('api/bikes');
+ cy.get('.json-container').contains('kawasaki ninja');
+ cy.get('.json-container').contains('kawasaki');
+ cy.get('.json-container').contains('2023');
+ cy.get('.json-container').contains('red');
+ cy.get('.json-container').contains('20000');
+ cy.get('.json-container').contains('POST');
+ });
+});
diff --git a/logs.png b/logs.png
new file mode 100644
index 0000000..ffbdb45
Binary files /dev/null and b/logs.png differ
diff --git a/package-lock.json b/package-lock.json
index ad49457..d939b15 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,6 +16,7 @@
"highlight.js": "^11.11.1",
"markdown-it": "^14.1.0",
"msw": "^2.7.4",
+ "pretty-print-json": "^3.0.4",
"tsx": "^4.19.3",
"typescript": "^5.8.3",
"zod": "^3.24.2"
@@ -9761,6 +9762,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/pretty-print-json": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pretty-print-json/-/pretty-print-json-3.0.4.tgz",
+ "integrity": "sha512-sVupP4x7magteGomyHFUiL8trOVVo45BP74gVo6IiRhQFp3qtmlqCdFt/Tjkim1+/Rr+P/wKl4p67d1BQ8h9bw==",
+ "license": "MIT"
+ },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
diff --git a/package.json b/package.json
index 2bf2910..7e8d0ee 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"highlight.js": "^11.11.1",
"markdown-it": "^14.1.0",
"msw": "^2.7.4",
+ "pretty-print-json": "^3.0.4",
"tsx": "^4.19.3",
"typescript": "^5.8.3",
"zod": "^3.24.2"
diff --git a/src/api/bikes/api.ts b/src/api/bikes/api.ts
index b69a9a7..da53e49 100644
--- a/src/api/bikes/api.ts
+++ b/src/api/bikes/api.ts
@@ -1,4 +1,5 @@
import { http, HttpResponse } from 'msw';
+import logger from '../../utilities/logger';
// Add any http handler here (get, push , delete etc., and middleware as needed)
@@ -9,6 +10,14 @@ function handler(pathName: string) {
const type = url.searchParams.get('type');
console.log(`starting ${pathName}`);
console.log('Item Type is', type);
+
+ // Log the request passing the request data, pathName and request type to the logger function
+ logger({
+ data: { type },
+ pathName,
+ type: 'GET',
+ });
+
return HttpResponse.json({
response: `this is a GET test response from ${pathName} for bike type: ${type ?? 'none'}`,
});
@@ -16,6 +25,14 @@ function handler(pathName: string) {
http.post(`/${pathName}`, async ({ request }) => {
// Get Body Data using json(), text() or formData() depending on what is sent
const bodyData = await request.json();
+
+ // Log the request passing the request data, pathName and extra information to the logger function
+ logger({
+ data: bodyData,
+ type: 'POST',
+ pathName,
+ });
+
return HttpResponse.json({
response: `this is a POST test response from ${pathName} with bodyData ${JSON.stringify(bodyData)}`,
});
diff --git a/src/logs/.gitkeep b/src/logs/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/server.ts b/src/server.ts
index ba4ed20..0c883f9 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -3,12 +3,24 @@ import { createServer } from '@mswjs/http-middleware';
import * as seeders from './seeders/index.js';
import getApiRoutes from './utilities/file-scan.js';
import serverPage from './utilities/server-page.js';
+import logPage from './utilities/log-page.js';
+import { deleteLogs } from './utilities/logger.js';
import { env } from './utilities/env.js';
const { apiHandlers, apiRoutes } = await getApiRoutes();
-const httpServer = createServer(...apiHandlers, ...serverPage(apiRoutes));
+const httpServer = createServer(
+ ...apiHandlers,
+ ...serverPage(apiRoutes),
+ ...logPage(),
+);
+// Delete any logs on server start if the DELETE_LOGS_ON_SERVER_RESTART env var is set to 'ON'
+if (process.env?.DELETE_LOGS_ON_SERVER_RESTART?.toUpperCase() === 'ON') {
+ deleteLogs();
+}
+
+// Set up the server to listen on the specified port
httpServer.listen(env.SERVER_PORT);
// Execute dB seeder functions
diff --git a/src/utilities/log-page.ts b/src/utilities/log-page.ts
new file mode 100644
index 0000000..374e2bd
--- /dev/null
+++ b/src/utilities/log-page.ts
@@ -0,0 +1,107 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { http, HttpResponse } from 'msw';
+import { prettyPrintJson } from 'pretty-print-json';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const createHtml = () => {
+ function readLogs() {
+ const logFolder = `${__dirname}/../logs`;
+ const logPath = path.join(logFolder, 'api_request_log.log');
+ let logs = '';
+ // Append the log entry to the log file
+ try {
+ logs = fs.readFileSync(logPath, 'utf8');
+ } catch {
+ logs =
+ '{"message": "No logs found", "solution": "Set LOG_REQUESTS env var to ON and add a logger function to a route api.ts. Restart the server then retry the request"}';
+ }
+
+ return `[${logs}]`;
+ }
+
+ const htmlString = `
+
+
* Add new api endpoints to the api folder.
For media endpoints include the media name in the url E.g /images/placeholder.png