Forem

Cover image for 10 JavaScript Best Practices for High-Performance Serverless Functions
Aarav Joshi
Aarav Joshi

Posted on

10 JavaScript Best Practices for High-Performance Serverless Functions

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Creating effective serverless functions requires careful attention to both code quality and performance optimization. Let me share my experience with proven JavaScript techniques that have consistently delivered results.

Function Startup Optimization

The initial function startup time significantly impacts user experience. I've found that moving initialization code outside the handler function reduces cold start latency substantially.

// Bad Practice
exports.handler = async (event) => {
  const db = new Database();
  const config = await loadConfig();
  // Handle request
};

// Good Practice
const db = new Database();
const config = loadConfig();

exports.handler = async (event) => {
  // Handle request directly
};
Enter fullscreen mode Exit fullscreen mode

Error handling is crucial for maintaining reliability. I implement a consistent error handling pattern across all functions:

const createErrorResponse = (code, message, details) => ({
  statusCode: code,
  body: JSON.stringify({
    error: {
      message,
      details,
      timestamp: new Date().toISOString()
    }
  })
});

exports.handler = async (event) => {
  try {
    // Function logic
  } catch (error) {
    console.error('Function error:', error);
    return createErrorResponse(500, 'Internal server error', error.message);
  }
};
Enter fullscreen mode Exit fullscreen mode

Input validation prevents issues before they occur. I use JSON Schema for robust validation:

const Ajv = require('ajv');
const ajv = new Ajv();

const schema = {
  type: 'object',
  properties: {
    userId: { type: 'string' },
    amount: { type: 'number', minimum: 0 }
  },
  required: ['userId', 'amount']
};

const validate = ajv.compile(schema);

exports.handler = async (event) => {
  const data = JSON.parse(event.body);

  if (!validate(data)) {
    return createErrorResponse(400, 'Invalid input', validate.errors);
  }

  // Process validated data
};
Enter fullscreen mode Exit fullscreen mode

Managing dependencies effectively is essential for maintaining lean functions. I analyze dependencies regularly and remove unused ones:

// package.json
{
  "dependencies": {
    "aws-sdk": "^2.1001.0",
    "ajv": "^8.6.3"
  },
  "devDependencies": {
    "jest": "^27.2.5"
  }
}
Enter fullscreen mode Exit fullscreen mode

Connection pooling improves performance significantly. I maintain connection pools outside the handler:

const { Pool } = require('pg');

const pool = new Pool({
  max: 1,
  connectionTimeoutMillis: 10000,
});

exports.handler = async (event) => {
  const client = await pool.connect();
  try {
    const result = await client.query('SELECT * FROM users');
    return {
      statusCode: 200,
      body: JSON.stringify(result.rows)
    };
  } finally {
    client.release();
  }
};
Enter fullscreen mode Exit fullscreen mode

Monitoring provides crucial insights. I implement comprehensive logging and metrics:

const startTime = process.hrtime();

const logMetrics = (operation, duration) => {
  console.log({
    operation,
    duration,
    memory: process.memoryUsage(),
    timestamp: new Date().toISOString()
  });
};

exports.handler = async (event) => {
  try {
    const result = await processRequest(event);
    const duration = process.hrtime(startTime);
    logMetrics('success', duration[0] * 1000 + duration[1] / 1000000);
    return result;
  } catch (error) {
    logMetrics('error', process.hrtime(startTime));
    throw error;
  }
};
Enter fullscreen mode Exit fullscreen mode

Testing ensures reliability. I create comprehensive test suites:

const { handler } = require('./index');

describe('Lambda Function', () => {
  test('processes valid input correctly', async () => {
    const event = {
      body: JSON.stringify({
        userId: '123',
        amount: 100
      })
    };

    const response = await handler(event);
    expect(response.statusCode).toBe(200);
  });

  test('handles invalid input', async () => {
    const event = {
      body: JSON.stringify({
        userId: '123'
      })
    };

    const response = await handler(event);
    expect(response.statusCode).toBe(400);
  });
});
Enter fullscreen mode Exit fullscreen mode

Response formatting should be consistent across all functions:

const createResponse = (code, data) => ({
  statusCode: code,
  headers: {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*'
  },
  body: JSON.stringify({
    data,
    timestamp: new Date().toISOString()
  })
});

exports.handler = async (event) => {
  const result = await processRequest(event);
  return createResponse(200, result);
};
Enter fullscreen mode Exit fullscreen mode

Memory management is crucial for serverless functions. I implement cleanup routines:

exports.handler = async (event) => {
  let tempResources = [];

  try {
    tempResources = await allocateResources();
    const result = await processRequest(event, tempResources);
    return createResponse(200, result);
  } finally {
    await Promise.all(tempResources.map(resource => resource.cleanup()));
  }
};
Enter fullscreen mode Exit fullscreen mode

Caching improves performance for frequently accessed data:

const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });

exports.handler = async (event) => {
  const cacheKey = `data_${event.pathParameters.id}`;

  let data = cache.get(cacheKey);
  if (!data) {
    data = await fetchDataFromDatabase();
    cache.set(cacheKey, data);
  }

  return createResponse(200, data);
};
Enter fullscreen mode Exit fullscreen mode

Security is paramount. I implement security best practices:

const crypto = require('crypto');

const validateToken = (token) => {
  const hash = crypto.createHash('sha256');
  const timestamp = Math.floor(Date.now() / 1000);
  return hash.update(`${token}${timestamp}`).digest('hex');
};

exports.handler = async (event) => {
  if (!validateToken(event.headers.authorization)) {
    return createResponse(401, { message: 'Unauthorized' });
  }

  // Process authorized request
};
Enter fullscreen mode Exit fullscreen mode

Performance optimization requires careful resource management:

const { performance } = require('perf_hooks');

exports.handler = async (event) => {
  const start = performance.now();

  try {
    if (performance.now() - start > 5000) {
      throw new Error('Operation timeout');
    }

    const result = await processRequest(event);
    return createResponse(200, result);
  } finally {
    console.log(`Operation took ${performance.now() - start}ms`);
  }
};
Enter fullscreen mode Exit fullscreen mode

These techniques form a robust foundation for building reliable serverless functions. Regular testing, monitoring, and optimization ensure optimal performance and reliability. The key is to maintain a balance between feature richness and operational efficiency while ensuring code maintainability and scalability.


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Top comments (0)