Referencing files using netlify functions - express.js

I have the following code in an expressApp.ts:

// functions/expressApp.ts

import express from 'express';
import path from 'path';
import serverless from 'serverless-http';
import axios from 'axios';

const app = express();

// Adjust basePath to point to the public directory at the root of the deployment environment
const basePath = path.resolve(__dirname, '..', 'public'); // Moving up one level from `functions`

console.log("Starting Express app...");
console.log("Base path for static files:", basePath);

// Set up middleware to serve static files from basePath
app.use(express.static(basePath));

// Define city coordinates
const cityCoordinates: { [key: string]: [number, number] } = {
    london: [51.508, -0.126],
    'new-york': [40.7128, -74.006],
};

// Function to capitalize city names
function capitalizeCityName(city: string): string {
    return city.split('-')
        .map(word => word.charAt(0).toUpperCase() + word.slice(1))
        .join(' ');
}

// Route to serve index.html for each city
app.get('/:city', async (req, res) => {
    const city = req.params.city.toLowerCase();

    if (!cityCoordinates[city]) {
        console.error(`City not found: ${city}`);
        res.status(404).send("City not found");
        return;
    }

    const indexPath = path.join(basePath, 'index.html');
    console.log(`Attempting to send file: ${indexPath}`);
    res.sendFile(indexPath, err => {
        if (err) {
            console.error("Error sending file:", err);
            res.status(500).send("Error serving content");
        }
    });
});

// Route to fetch data for each city
app.get('/api/places/:city', async (req, res) => {
    const city = req.params.city.toLowerCase();

    if (!cityCoordinates[city]) {
        res.status(404).send("City not found");
        return;
    }

    const airtableApiUrl = 'https://api.airtable.com/v0/[airtable base ID present in code]/Places';
    const apiKey = process.env.AIRTABLE_API_KEY;

    async function fetchDataWithPagination(offset: string = ''): Promise<any[]> {
        try {
            const response = await axios.get(airtableApiUrl, {
                headers: {
                    Authorization: `Bearer ${apiKey}`,
                },
                params: {
                    offset: offset,
                    filterByFormula: `{City} = '${capitalizeCityName(city)}'`
                },
            });

            const records = response.data.records;
            const newOffset = response.data.offset;

            if (newOffset) {
                return records.concat(await fetchDataWithPagination(newOffset));
            } else {
                return records;
            }
        } catch (error) {
            console.error("Error fetching data:", error);
            throw error;
        }
    }

    try {
        const allRecords = await fetchDataWithPagination();

        res.json({
            coordinates: cityCoordinates[city],
            data: allRecords,
        });
    } catch (error) {
        console.error("Error fetching data:", error);
        res.status(500).send("Server error");
    }
});

// Export the serverless function handler
export const handler = serverless(app);

and the following as my netlify.toml:

[build]
  command = "npm install && npm run build"
  publish = "functions"
  functions = "functions"
[functions]
  external_node_modules = ["express"]
  node_bundler = "esbuild"
[[redirects]]
  force = true
  from = "/*"
  status = 200
  to = "/.netlify/functions/expressApp/:splat"

When I deploy to netlify (although it doesn’t work in dev either) I get the following error in my functions logs:

ERROR  Error sending file: [Error: ENOENT: no such file or directory, stat '/var/task/public/index.html'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'stat',
  path: '/var/task/public/index.html',
  expose: false,
  statusCode: 404,
  status: 404
}

My file structure is:

netlify.toml
functions/
- expressApp.ts
- public/
-- index.html

which appears to get converted on deploy to

expressApp.ts
- public/
-- index.html

Any idea what is going wrong referencing my static files?

Hi! I had a similar issue and I made sure that the public folder was available from the root project, have you tried setting the directory out of the functions one?

Thanks. I changed that and changed my netify.toml to:

[build]
  command = "npm install && npm run build"
  functions = "functions"
[functions]
  external_node_modules = ["express"]
  node_bundler = "esbuild"
[[redirects]]
  force = true
  from = "/*"
  status = 200
  to = "/.netlify/functions/expressApp/:splat"

(ie removed the public line)

Still the same error as above.

Here is what the Netlify dashboard shows as having been built:

Under public is:

_redirects
index.html
main.js
styles.css

In the _redirects file there is /* /index.html 200

Following the documentation and AI chatbot it seems that there’s this piece missing under the functions section:

[functions]
directory = "functions"

You want included_files

included_files: list of additional paths to include in the function bundle. Although our build system includes statically referenced files (like require("./some-file.js")) by default, included_files lets you specify additional files or directories and reference them dynamically in function code. You can use * to match any character or prefix an entry with ! to exclude files. Paths are absolute paths relative to the base directory.

Also check out the Express on Netlify documentation

included_files and directory = "functions" did the trick - thank you!