Skip to content

Propagate framework errors to user application #686

Open

Description

@gregorygaines

Currently, errors that happen in the framework express layer are passed to the default express error handler. The default error handler returns the error to the client with a stack trace if in non-prod enviornments. It would be useful for certain cases for the user to have context of what happened during a request instead of the user being bypassed entirely. Instead, if the user has an error handler middleware installed in their application, we should pass the framework error to the user's error handler.

For example, if the framework gets a request with a bad json body.

const app = express():

app.post("/", (req, res) => {
 ...
});

// User error handler
app.use((err, req, res, next) => {
  logger.log(err);
  res.send("Caught error!");
});

functions.http("function, app);
// Post request with bad JSON
http.post("/", "{"id": "Hello}");

The framework responds with the following not very helpful message from the Express default error handler:

SyntaxError: Expected double-quoted property name in JSON at position 20 (line 3 column 1)
    at JSON.parse (<anonymous>)
    at parse (functions-framework-nodejs/node_modules/body-parser/lib/types/json.js:92:19)
    at functions-framework-nodejs/node_modules/body-parser/lib/read.js:128:18
    at AsyncResource.runInAsyncScope (node:async_hooks:211:14)
    at invokeCallback (functions-framework-nodejs/node_modules/raw-body/index.js:238:16)
    at done (functions-framework-nodejs/node_modules/raw-body/index.js:227:7)
    at IncomingMessage.onEnd (functions-framework-nodejs/node_modules/raw-body/index.js:287:7)
    at IncomingMessage.emit (node:events:518:28)
    at endReadableNT (node:internal/streams/readable:1698:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21)

There should be an option to propagate errors to the user client applcation, so their error handler is triggered and can preform logging, mitigations, or any other action the user wants instead of ignoring and leaving their application in the dust.

I've created #685 to address this issue.

Activity

  1. chatelgu commented on Jun 25, 2025

    @chatelgu

    Hi,

    Facing the same issue : I need to handle json parsing errors in the way the specs I implement specify it.

    Maybe, it would be easier just to have a way to deactivate the json parsing inside the framework and let the function code (express style) parse the body and handle potential errors.

    Something like --disableMiddleware so in the server.js ligne 90, we can have :

      // Apply middleware
      if (options.disableMiddleware !== true) {
        app.use(bodyParser.json(cloudEventsBodySavingOptions));
        app.use(bodyParser.json(defaultBodySavingOptions));
        app.use(bodyParser.text(defaultBodySavingOptions));
        app.use(bodyParser.urlencoded(urlEncodedOptions));
      }

    Is it something that can suit you @gregorygaines ?

    Question to @jihuin, waiting for a integration, is it possible to deploy a function with a custom functions-framework-nodejs ?

  2. gregorygaines commented on Jun 25, 2025

    @gregorygaines
    Author

    Let me think more on this. In my case, if I pass an error handler to my application its only triggered through certain situtations but not all since the parent application installs its own error handler. Let me see if your solution works.

  3. PeerLabs commented on Sep 10, 2026

    @PeerLabs

    Adding a minimal repro in support of this, against 5.0.5.

    index.js:

    const functions = require("@google-cloud/functions-framework");
    const express = require("express");
    
    const app = express();
    app.use(express.json());
    app.post("/", (req, res) => res.json({ ok: true, body: req.body }));
    // The application's own error handler. Never reached for a malformed body.
    app.use((err, req, res, next) => {
      console.log("user error handler reached:", err.type);
      res.status(400).json({ error: "handled by the application" });
    });
    
    functions.http("fn", app);
    $ NODE_ENV=production npx functions-framework --target=fn --port=18080
    $ curl -si -X POST -H 'Content-Type: application/json' --data '{bad json' localhost:18080/
    HTTP/1.1 400 Bad Request
    Content-Type: text/html; charset=utf-8
    
    <pre>Bad Request</pre>
    

    A valid body returns {"ok":true,"body":{"a":1}}, and the malformed one never logs user error handler reached.

    Cause: build/src/server.js (5.0.5, lines 82-88) mounts bodyParser.json / text / urlencoded / raw ahead of the user function, with no error-handling middleware after them. body-parser's entity.parse.failed error therefore falls through to Express's default finalhandler. The function's own express.json() is a no-op (the body is already consumed and req._body is set), so nothing in the application can shape this response.

    Why it matters: for a JSON API whose clients parse a JSON error envelope, the one error they can't parse is the malformed-request error, and there's no way to fix it from inside the function.

    Either shape discussed here would solve it:

    1. Forward the parse error to the user function (for example, leave req.body unset with req.rawBody available and the error attached), so the application's own error handling applies.
    2. An opt-out of the built-in parsers for HTTP functions, as in the --disableMiddleware suggestion above.

    Option 1 looks less disruptive for existing users of req.body / req.rawBody.

    For anyone arriving from Firebase: the Firebase functions emulator (firebase-tools, lib/emulator/functionsEmulatorRuntime.js) mounts the same parsers the same way, so local emulation behaves identically (with a stack trace in the page when NODE_ENV isn't production).

  4. blackakula commented on Oct 22, 2026

    @blackakula

    Another data point on 5.0.5 (Node.js 24, Cloud Functions gen2). The HTML error page is only part of it. A malformed body is rejected before the function runs, so nothing in the function runs for that request:

    • Authentication. An unauthenticated request with a malformed body gets a 400 instead of a 401, so the response shows a request got past the auth layer that never ran.
    • Logging and metrics. No request-scoped log line and no metric are emitted. In Cloud Logging, the only trace is the framework's stack trace from body-parser, without the execution context the function would normally add.
    • Response contract. An API that publishes an error format (JSON:API, RFC 7807 problem details, …) can't meet it.
    exports.handler = (req, res) => {
      console.log('handler reached'); // never printed for a malformed body
      res.json({ ok: true });
    };
    curl -si -H 'Content-Type: application/json' -d '{"data":' localhost:8080
    # HTTP/1.1 400 Bad Request
    # Content-Type: text/html; charset=utf-8
    # <pre>Bad Request</pre>            (NODE_ENV=production; otherwise the full stack trace)

    +1 to the earlier suggestion of a switch that turns off the framework's body parsing (and leaves rawBody intact). Given the concern in #255 about exposing middleware, that seems the smallest API surface. It would solve this issue and also the application/*+json media types landing as a raw Buffer (see #232). The function would parse the body itself and own the error response. PR #685 would also work for us, as long as the error handler runs in the function's context (where auth and logging live).

    Workaround we run in production, in case it helps others. It is fragile and depends on internals. The framework loads user code before it builds its express app, so the user module can replace the json export of the body-parser instance the framework resolves. application/json bodies then fall through to the raw parser, and the function parses req.body (a Buffer) itself:

    const { createRequire } = require('node:module');
    
    const frameworkRequire = createRequire(require.resolve('@google-cloud/functions-framework'));
    const bodyParser = frameworkRequire('body-parser');
    const json = bodyParser.json;
    // The cloudevents parser passes an explicit `type` and is left alone.
    bodyParser.json = options => json({ ...options, type: options.type ?? (() => false) });

    We pair it with a check that logs a warning if a JSON body ever arrives already parsed, so a framework upgrade that breaks the patch shows up. A supported option would let us remove all of this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions