How to deploy Node.js app to Netlify functions

Deploying Node.js applications to Netlify Functions provides serverless API endpoints with automatic deployment, Git integration, and edge distribution. With over 12 years of Node.js experience since 2014 and as the creator of CoreUI, I’ve deployed numerous serverless functions to Netlify. Netlify Functions run on AWS Lambda providing scalable backend logic without server management with automatic HTTPS and custom domains. This approach works perfectly for API routes, form handlers, webhooks, and backend services with instant deployment on Git push.

Create functions in netlify/functions directory and deploy via Git for automatic serverless Node.js deployment.

Basic Netlify function:

// netlify/functions/hello.js
exports.handler = async (event, context) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Hello from Netlify Functions!',
      timestamp: new Date().toISOString()
    })
  }
}

// Accessible at: /.netlify/functions/hello

REST API endpoints:

// netlify/functions/users.js
exports.handler = async (event, context) => {
  const { httpMethod, path, body, queryStringParameters } = event

  // Enable CORS
  const headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE'
  }

  // Handle preflight
  if (httpMethod === 'OPTIONS') {
    return { statusCode: 200, headers, body: '' }
  }

  try {
    switch (httpMethod) {
      case 'GET':
        // Get user by ID from query params
        const userId = queryStringParameters?.id
        return {
          statusCode: 200,
          headers,
          body: JSON.stringify({
            id: userId,
            name: 'John Doe',
            email: '[email protected]'
          })
        }

      case 'POST':
        // Create user
        const userData = JSON.parse(body)
        return {
          statusCode: 201,
          headers,
          body: JSON.stringify({
            success: true,
            user: userData
          })
        }

      case 'PUT':
        // Update user
        const updateData = JSON.parse(body)
        return {
          statusCode: 200,
          headers,
          body: JSON.stringify({
            success: true,
            updated: updateData
          })
        }

      case 'DELETE':
        // Delete user
        return {
          statusCode: 204,
          headers,
          body: ''
        }

      default:
        return {
          statusCode: 405,
          headers,
          body: JSON.stringify({ error: 'Method not allowed' })
        }
    }
  } catch (error) {
    return {
      statusCode: 500,
      headers,
      body: JSON.stringify({ error: error.message })
    }
  }
}

Environment variables:

// netlify/functions/config.js
exports.handler = async (event, context) => {
  // Access environment variables
  const apiKey = process.env.API_KEY
  const dbUrl = process.env.DATABASE_URL

  return {
    statusCode: 200,
    body: JSON.stringify({
      environment: process.env.CONTEXT, // production, deploy-preview, branch-deploy
      hasApiKey: !!apiKey,
      hasDbUrl: !!dbUrl
    })
  }
}

// Set via Netlify UI: Site settings > Environment variables
// Or in netlify.toml

netlify.toml configuration:

[build]
  functions = "netlify/functions"
  publish = "dist"

[functions]
  node_bundler = "esbuild"

[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200

[context.production.environment]
  NODE_ENV = "production"

[context.deploy-preview.environment]
  NODE_ENV = "development"

Database integration:

// netlify/functions/db-users.js
import { Client } from 'pg'

exports.handler = async (event, context) => {
  const client = new Client({
    connectionString: process.env.DATABASE_URL,
    ssl: { rejectUnauthorized: false }
  })

  try {
    await client.connect()

    const result = await client.query('SELECT * FROM users LIMIT 10')

    return {
      statusCode: 200,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        users: result.rows,
        count: result.rowCount
      })
    }
  } catch (error) {
    console.error('Database error:', error)
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Database query failed' })
    }
  } finally {
    await client.end()
  }
}

// package.json
{
  "dependencies": {
    "pg": "^8.11.3"
  }
}

Scheduled functions:

// netlify/functions/scheduled-task.js
import fetch from 'node-fetch'

exports.handler = async (event, context) => {
  console.log('Scheduled function executed at:', new Date().toISOString())

  try {
    // Perform scheduled task
    const response = await fetch('https://api.example.com/sync')
    const data = await response.json()

    return {
      statusCode: 200,
      body: JSON.stringify({
        success: true,
        data: data
      })
    }
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message })
    }
  }
}

// netlify.toml
[[functions]]
  path = "scheduled-task"
  schedule = "0 0 * * *"  # Daily at midnight

Background functions:

// netlify/functions/background-process.js
exports.handler = async (event, context) => {
  // Start background task
  setTimeout(() => {
    console.log('Background task completed')
    // This runs after function returns
  }, 5000)

  // Return immediately
  return {
    statusCode: 202,
    body: JSON.stringify({
      message: 'Background task started'
    })
  }
}

// netlify.toml
[[functions]]
  path = "background-process"
  [functions.background-process]
    background = true

Form handling:

// netlify/functions/contact-form.js
import nodemailer from 'nodemailer'

exports.handler = async (event, context) => {
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' }
  }

  try {
    const { name, email, message } = JSON.parse(event.body)

    // Validate input
    if (!name || !email || !message) {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'Missing required fields' })
      }
    }

    // Send email
    const transporter = nodemailer.createTransport({
      host: process.env.SMTP_HOST,
      port: process.env.SMTP_PORT,
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS
      }
    })

    await transporter.sendMail({
      from: email,
      to: '[email protected]',
      subject: `Contact form: ${name}`,
      text: message
    })

    return {
      statusCode: 200,
      body: JSON.stringify({ success: true })
    }
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message })
    }
  }
}

Authentication middleware:

// netlify/functions/utils/auth.js
export function verifyToken(event) {
  const token = event.headers.authorization?.replace('Bearer ', '')

  if (!token) {
    return { authorized: false, error: 'No token provided' }
  }

  try {
    // Verify JWT token
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    return { authorized: true, user: decoded }
  } catch (error) {
    return { authorized: false, error: 'Invalid token' }
  }
}

// netlify/functions/protected.js
import { verifyToken } from './utils/auth'

exports.handler = async (event, context) => {
  const auth = verifyToken(event)

  if (!auth.authorized) {
    return {
      statusCode: 401,
      body: JSON.stringify({ error: auth.error })
    }
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Protected data',
      user: auth.user
    })
  }
}

Package.json:

{
  "name": "netlify-functions",
  "version": "1.0.0",
  "scripts": {
    "dev": "netlify dev",
    "build": "netlify build",
    "deploy": "netlify deploy --prod"
  },
  "dependencies": {
    "node-fetch": "^2.7.0",
    "pg": "^8.11.3"
  },
  "devDependencies": {
    "netlify-cli": "^17.10.1"
  }
}

Local development:

# Install Netlify CLI
npm install -g netlify-cli

# Login
netlify login

# Init project
netlify init

# Run locally (with functions)
netlify dev

# Test function locally
curl http://localhost:8888/.netlify/functions/hello

# Deploy to production
netlify deploy --prod

GitHub integration:

# Connect repository via Netlify UI
# Automatic deployments on:
# - Push to main branch (production)
# - Pull requests (deploy previews)

# Build settings in Netlify UI:
# Build command: npm run build
# Publish directory: dist
# Functions directory: netlify/functions

Best Practice Note

Keep functions in netlify/functions directory for automatic deployment. Use environment variables for secrets via Netlify UI. Functions timeout after 10 seconds (26 seconds for background). Enable CORS headers for API endpoints. Use redirects in netlify.toml for cleaner URLs. Implement proper error handling and logging. Keep functions lightweight and focused. Use background functions for long-running tasks. This is how we deploy serverless functions with Netlify—automatic deployment from Git, environment configuration, and scalable backend logic without server management.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author