> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://cla-demo.postmansolutions.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://cla-demo.postmansolutions.com/_mcp/server.

# NodeJS Code Structure

This project is a **Fastify-based application** that integrates with **Azure Service Bus** for message processing. It follows a **modular and scalable structure** to ensure maintainability.

### Project Structure

📂 **node-project**\
├── 📂 **.docker**                   # Dockerfiles and docker-compose.yaml\
├── 📂 **database**                  # Temporary location for our database schema scripts\
├── 📂 **dist**                      # TypeScript compiler results (not committed)\
├── 📂 **reports**                   # Code quality reports (currently only coverage reports)\
├── 📂 **src**                       # All of the implementation source code\
│   ├── 📂 **config**                    # Configuration files (e.g., Azure Service Bus host settings)\
│   ├── 📂 **controllers**           # API request/response handlers and schema\
│   ├── 📂 **internals**             # Reusable code that could become a new node package\
│   ├── 📂 **routes**                # API route definitions\
│   ├── 📂 **services**              # Business logic (upload, copy file, send to extractor, etc...)\
│   ├── 📂 **subscribers**           # Azure Service Bus message subscribers\
│   ├── 📂 **types**                 # Type definitions that get re-used in different parts of the pp\
│   ├── 📂 **utils**                 # Junk drawer (Ideally empty)\
│   └── 📄 **index.ts**              # Initializes and starts the application\
├── 📂 **test**                      # ALL automated tests\
│   └── 📂 **unit**                  # ALL Unit tests (ONLY Unit tests)\
├── 📄 **.env**                      # Environment variables FOR CONFIGURATION (not committed)\
├── 📄 **.npmrc**                    # Configuration that allows us to use our private npm registry\
├── 📄 **.gitignore**                # Files and folders for git to ignore\
├── 📄 **eslint.config.ts**          # ESLint rules configuration\
├── 📄 **package.json**              # Dependencies and scripts\
├── 📄 **package-lock.json**         # Used by npm to fetch consistent dependencies (not committed)\
├── 📄 **pull\_request\_template.md**  # Pull request template with the checklist in it\
├── 📄 **README.md**                 # Information about this project\
├── 📄 **tsconfig.json**             # TypeScript compiler configuration\
└── 📄 **vitest.config.ts**          # Test configuration for vitest framework

---

## **📂 `config` - Configuration**

Stores configuration files such as **Azure Service Bus credentials**.

Not TypeScript files unless that is the config file format that is required.

### **📄 `config/appConfig.json`**

```json
{
  "port": 3000,
  "azureServiceBusConnectionString": 'SomeActualServiceBusConnectionString',
};
```

---

## **📂 `src` - All of the TypeScript implementation files for our microservice**

## **📂 `src/controllers` - API request/response handlers and schema**

### **📄 `src/controllers/message.ts`**

```typescript
import { azureServiceBusService } from '../../services/azureServiceBusService.js';

export const messageController = {
  send: async (request: FastifyRequest, reply: FastifyReply) => {
    try {
      const message = request.body?.message;
      await azureServiceBusService.sendMessageToQueue('message-queue', message);
      return reply.send({ status: 'Message sent' });
    } catch (error) {
      request.log.error(error);
      await reply.status(500).send(formatErrorAsProblemJson(error));
      throw error;
    }
  }
};

export const messageSchema = {  
    body: {  
        type: 'object',  
        properties: {  
            name: { type: 'string' },  
            excitement: { type: 'integer' }  
        }  
    },  
    response: {  
        200: {  
            type: 'object',  
            properties: {  
                hello: { type: 'string' }  
            }  
        }  
    }  
}
```

---

## **📂 `src/internals` - Reusable code that could become a new node package**

As we discover functionality that we want to reuse we can create a subfolder here to
hold all of the related functionality.  For example, if we begin by subscribing to a
single Service Bus topic but later realize we need to subscribe to more than one we
could create a subfolder here called something like "serviceBusSubscribe" and we could
put a general purpose function that can take a topic name and a function to handle each
message so that when we need to create a subscriber for a particular topic we can do it
the same way each time.  This repetition might be within the current project OR across
multiple microservices.

There could be other reasons to create a separate node package for some of our code.  One
example is that we have created a feature that takes a long time to run the test suite
for and which we change very seldom.  If we put that code and the related tests into a
separate package we can pay the price of running those tests only when we need to change
that feature.  So if that test suite took 2 minutes to run, instead of waiting 2 minutes
in our microservice for each test run, we can save 2 minutes for each test run in the microservice
and only need that 2 minute test suite when we are changing that package.

---

## **📂 `src/routes` - All route assignments for our REST API routes**

### **📄 `src/routes/customeRoute.ts`**

```typescript
import { FastifyInstance } from 'fastify';
import { messageController, messageSchema } from '../controllers/message.js';

export const customeRoute = async (fastify: FastifyInstance) {
  fastify.post('/send', { schema: messageSchema },  
    async (request: FastifyRequest, reply: FastifyReply) => {
      await messageController.send(request, reply);
    });
}
```

---

## **📂 `src/plugins` - Fastify Plugins**

**(PLEASE DO NOT USE THIS FOLDER UNTIL WE TALK ABOUT IT)**

Handles **database connections** and **Azure Service Bus setup**.

### **📄 `src/plugins/azureServiceBus.ts`**

```typescript
import { ServiceBusClient } from '@azure/service-bus';
import { config } from '../config/index.js';

let client: ServiceBusClient;

export const azureServiceBusPlugin = async function (fastify: any) {
  try {
    client = new ServiceBusClient(config.azureServiceBusConnectionString);
    fastify.decorate('azureServiceBus', client);
    fastify.log.info('Azure Service Bus Connected');
  } catch (err) {
    fastify.log.error('Azure Service Bus Connection Error:', err);
    process.exit(1);
  }
};

export { client };
```

---

## **📂 `src/services` - Business Logic**

Contains logic to send messages to **Azure Service Bus**.

### **📄 `src/services/azureServiceBusService.ts`**

```typescript
import { ServiceBusClient } from '@azure/service-bus';
import { config } from '../config/index.js';

const client = new ServiceBusClient(config.azureServiceBusConnectionString);
const sender = client.createSender('message-queue');

export const enqueue = async (queueName: string, message: object) => {
  try {
    await sender.sendMessages({ body: JSON.stringify(message) });
    console.log(`Message sent to Azure Service Bus queue: ${queueName}`);
  } catch (error) {
    console.error('Error sending message to Azure Service Bus:', error);
  }
};
```

---

## **📂 `src/subscribers` - Message Consumers**

Consumes messages from **Azure Service Bus** and processes them.  If we find a source of messages other than the Azure Service Bus, it seems like handlers for those messages belong here too.

### **📄 `src/subscribers/messageQueueSubscriber.ts`**

```typescript
import { ServiceBusClient } from '@azure/service-bus';
import config from '../config/index.js';

const client = new ServiceBusClient(config.azureServiceBusConnectionString);
const receiver = client.createReceiver('message-queue');

const startConsumer = async () => {
  receiver.subscribe({
    processMessage: async (message) => {
      console.log('📩 Received Message:', message.body);
    },
    processError: async (error) => {
      console.error('Error processing message:', error);
    },
  });

  console.log('Azure Service Bus Consumer started...');
};

// This is an older example.  Please do not do this in real code
startConsumer().catch(console.error);
```

---

## **📄 `src/index.ts` - Framework Initialization and Entry Point**

Initializes **DevFramework**, registers plugins, enables features, and starts the server.

```typescript
import { DevFramework } from "framework-nodejs-core";
import 'framework-nodejs-appconfig';
    
// Initialize the framework
const app = await DevFramework.create();
    
// Enable middleware
await app.enableMiddleware();
    
// Initialize the appConfig
await app.initAppConfig({
  azureAppConfigOptions: {
    useManagedIdentity: true,
    endpoint: process.env.FRAMEWORK_AZURE_APP_CONFIG_ENDPOINT,
  },
  useEnvironmentVariables: true,
});
    
// Start the server
app.start(3000);
```

---

## **📂 test/unit -  ALL Unit tests (ONLY Unit tests)**

The folder structure in the test/unit folder should mirror the folder structure in the
src folder.  In other words, if there is a src/services/awesomeService.ts then the unit
tests would be in test/unit/services/awesomeService.test.ts.

---

## **📄 `.env` - Environment Variables**

**(PLEASE HELP MAKE SURE THIS DOES NOT GET COMMITED TO THE REPO)**

```
PORT=3000
AZURE_SERVICE_BUS_CONNECTION_STRING=your-azure-service-bus-connection-string
```

---

## **📄 `package.json` - NodeJS Package Configuration & Dependencies**

This example file assumes we are building a service with a REST API using the
CLA DevFramework libraries using vitest as a test-framework.

```json
{
  "name": "node-project",
  "version": "1.0.0",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "clean": "npx rimraf dist reports",
    "lint": "eslint .",
    "lint-fix": "eslint . --fix",
    "prebuild": "npx rimraf dist",
    "precoverage": "npx rimraf reports",
    "start": "tsc && node .",
    "test": "vitest run",
    "test-watch": "vitest --watch",
    "coverage": "vitest run --coverage"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@eslint/js": "^9.20.0",
    "@stylistic/eslint-plugin-js": "^4.0.1",
    "@types/node": "22.13.16",
    "@types/npmcli__package-json": "^4.0.4",
    "@vitest/coverage-v8": "^3.0.7",
    "eslint": "^9.20.1",
    "eslint-plugin-import": "^2.31.0",
    "eslint-plugin-n": "^17.15.1",
    "eslint-plugin-unused-imports": "^4.1.4",
    "jiti": "^2.4.2",
    "rimraf": "^6.0.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.2",
    "typescript-eslint": "^8.29.0",
    "vitest": "^3.0.9",
    "vitest-mock-extended": "^3.1.0"
  },
  "dependencies": {
    "@fastify/awilix": "^7.0.0",
    "@fastify/multipart": "^9.0.3",
    "@npmcli/package-json": "^6.1.1",
    "bson-objectid": "^2.0.4",
    "dotenv": "^16.4.7",
    "framework-nodejs-appconfig": "^1.0.11",
    "framework-nodejs-core": "^1.0.23",
    "framework-nodejs-extensions": "^1.0.2",
    "framework-nodejs-logging": "^1.0.8",
    "framework-nodejs-web": "^1.0.10",
    "pino-pretty": "^13.0.0"
  }
}
```