Seamless Integration: Hapi, Azure, and TypeScript Package Management
Modern web development often demands a cohesive strategy for backend APIs, cloud infrastructure, and dependency management. This post demonstrates how to integrate Hapi.js, Microsoft Azure, and robust package management practices within a single TypeScript application, offering a scalable and secure foundation for your projects.
Project Setup with Hapi.js and TypeScript
We'll start by initializing a new TypeScript project and setting up a basic Hapi server. Hapi is a rich framework for building APIs, known for its configuration-centric approach and extensive plugin ecosystem.
First, create a new directory and initialize a Node.js project:
mkdir hapi-azure-app
cd hapi-azure-app
npm init -y
npm install @hapi/hapi @hapi/boom dotenv @azure/keyvault-secrets @azure/identity
npm install --save-dev typescript @types/node @types/hapi__hapi @types/hapi__boom
npx tsc --init
Your package.json will now reflect these dependencies. Ensure tsconfig.json is configured for your environment (e.g., "target": "es2022", "module": "commonjs" or "esnext").
Next, create src/server.ts for your Hapi application:
import Hapi from '@hapi/hapi';
import 'dotenv/config'; // Loads .env variables
interface ServerConfig {
port: number;
host: string;
}
const init = async () => {
const serverConfig: ServerConfig = {
port: parseInt(process.env.PORT || '3000', 10),
host: process.env.HOST || 'localhost',
};
const server = Hapi.server(serverConfig);
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return { message: 'Hello from Hapi!' };
},
});
server.route({
method: 'GET',
path: '/api/status',
handler: (request, h) => {
return { status: 'OK', uptime: process.uptime() };
},
});
await server.start();
console.log(`Server running on ${server.info.uri}`);
};
process.on('unhandledRejection', (err) => {
console.error(err);
process.exit(1);
});
init();
Add a start script to package.json:
{
"name": "hapi-azure-app",
"version": "1.0.0",
"description": "",
"main": "dist/server.js",
"scripts": {
"build": "npx tsc",
"start": "node dist/server.js",
"dev": "npx tsc -w & nodemon dist/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@azure/identity": "^4.0.0",
"@azure/keyvault-secrets": "^4.8.0",
"@hapi/boom": "^10.0.0",
"@hapi/hapi": "^21.3.0",
"dotenv": "^16.4.5"
},
"devDependencies": {
"@types/hapi__hapi": "^21.0.0",
"@types/node": "^20.12.7",
"nodemon": "^3.1.0",
"typescript": "^5.4.5"
}
}
Run npm run build then npm start to see your Hapi server in action.
Secure Configuration with Azure Key Vault
For production applications, sensitive information like API keys and database connection strings should never be hardcoded. Azure Key Vault provides a secure store for secrets, keys, and certificates. We'll integrate it to fetch a hypothetical API key.
First, create an Azure Key Vault instance and add a secret (e.g., MyApiKey). Then, ensure your Azure App Service (or local development environment) has appropriate access policies set up, typically using a Managed Identity for Azure services or environment variables for local development (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET).
Update src/server.ts:
import Hapi from '@hapi/hapi';
import 'dotenv/config';
import { SecretClient } from '@azure/keyvault-secrets';
import { DefaultAzureCredential } from '@azure/identity';
// ... (interface ServerConfig and init function start)
const server = Hapi.server(serverConfig);
let mySecretValue: string | undefined;
// Fetch secret from Azure Key Vault
if (process.env.KEY_VAULT_NAME) {
const keyVaultName = process.env.KEY_VAULT_NAME;
const keyVaultUri = `https://${keyVaultName}.vault.azure.net`;
const credential = new DefaultAzureCredential(); // Uses Managed Identity or AZURE_ environment variables
const client = new SecretClient(keyVaultUri, credential);
try {
const secret = await client.getSecret('MyApiKey'); // Name of your secret in Key Vault
mySecretValue = secret.value;
console.log('Successfully fetched secret from Azure Key Vault.');
} catch (error) {
console.error('Failed to fetch secret from Azure Key Vault:', error);
// In production, you might want to stop the server or use a fallback.
}
} else {
console.warn('KEY_VAULT_NAME not set. Skipping Azure Key Vault integration.');
}
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return { message: 'Hello from Hapi!', secretStatus: mySecretValue ? 'fetched' : 'not fetched' };
},
});
// ... (other routes and server start)
Set KEY_VAULT_NAME in your .env file (for local testing with service principal credentials) or as an Application Setting in Azure App Service.
Deployment to Azure App Service
Deploying your Hapi application to Azure App Service provides a fully managed platform. App Service can host Node.js applications directly. You can use git push azure master or Azure DevOps/GitHub Actions for CI/CD.
For Node.js apps, Azure App Service automatically looks for a package.json with a start script. Our npm run build and npm start scripts are perfect for this. Ensure your main entry in package.json points to the compiled JavaScript file (dist/server.js).
When deploying, remember to configure Application Settings in Azure App Service for PORT, HOST, KEY_VAULT_NAME, and any Azure AD application credentials (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET) if not using Managed Identity.
Package Management Best Practices
Effective package management is crucial for maintainability and security. Always commit your package-lock.json (or yarn.lock) file to ensure consistent installations across environments. This locks dependency versions, preventing unexpected breaking changes.
dependenciesvsdevDependencies: Usedependenciesfor packages required at runtime (like@hapi/hapi) anddevDependenciesfor tools needed only during development or build (liketypescript,nodemon).- NPM Audit: Regularly run
npm auditto check for security vulnerabilities in your dependencies and update them promptly usingnpm audit fixor by manually updating versions. - Semantic Versioning: Understand
^(caret) and~(tilde) prefixes inpackage.json.^1.2.3means compatible with version1.2.3and above, up to2.0.0(non-breaking changes).~1.2.3means compatible with1.2.3and above, up to1.3.0(patch updates).
Conclusion
By integrating Hapi.js for a robust API layer, leveraging Azure Key Vault for secure configuration, deploying to Azure App Service, and adhering to sound package management practices, you can build and operate powerful, scalable, and secure TypeScript applications. This synergy empowers developers to focus on features while relying on proven tools for infrastructure and dependency hygiene.