Search code examples
node.jsexpress

Express returning incorrect 200 status code even after explicitly setting the code


I have the following code written in TypeScript and using Express:


  app.use((req, res, next) => {
    if (!process.env.UNIVERSAL_FS_PASSWORD) {
      throw new Error(
        "An environment variable UNIVERSAL_FS_PASSWORD is required to protect your files"
      );
    }

    if (!req.headers.authorization) {
      return res
        .json({success: false, error: "An Authorization header is required"})
        .status(401);
    }

    if (
      !bycrypt.compareSync(
        process.env.UNIVERSAL_FS_PASSWORD,
        (req.headers.authorization as string).replace(/^Bearer\s/, "")
      )
    ) {
      return res
        .json({success: false, error: "Unauthorized request"})
        .status(401);
    }

    next();
  });

It uses middleware to check for authorization and other key details. When I send an invalid authorization header I get the expected response. However, the status code is 200.

Here are my HTTP headers: | Key | Value | | -------- | -------------- | | Accept | */* | | User-Agent | Thunder Client (https://www.thunderclient.com) | | Authorization | test

I've tried using Thunder client and postman as well as the browser. Same 200 status code. Here is trimmed version of the full code

const initServer = () => {
  const app = express();
  const port = 3000;

  app.use(express.json());

  app.use((req, res, next) => {
    if (!process.env.UNIVERSAL_FS_PASSWORD) {
      throw new Error(
        "An environment variable UNIVERSAL_FS_PASSWORD is required to protect your files"
      );
    }

    if (!req.headers.authorization) {
      return res
        .json({success: false, error: "An Authorization header is required"})
        .status(401);
    }

    if (
      !bycrypt.compareSync(
        process.env.UNIVERSAL_FS_PASSWORD,
        (req.headers.authorization as string).replace(/^Bearer\s/, "")
      )
    ) {
      return res
        .json({success: false, error: "Unauthorized request"})
        .status(401);
    }

    next();
  });

  app.use((req, res, next) => {
    if (!req.query.method || req.query.method === "") {
      return res.json({error: "A method is required"}).status(422);
    }

    if (req.method === "POST" && !req.body) {
      return res
        .json({error: "A body is required on post requests"})
        .status(422);
    }
    next();
  });

  app.get("/:path", async (req, res) => {
    switch (req.query.method) {
      case "readFile":
        let fileOptions: {
          encoding?: null | undefined;
          flag?: string | undefined;
        } | null = null;
        let fileBuffer: Buffer | null = null;

        if (isJson(req.headers.options as string)) {
          fileOptions = JSON.parse(req.headers.options as string);
        }

        try {
          fileBuffer = fs.readFileSync(req.params.path, fileOptions);

          return res.json({success: true, buffer: fileBuffer});
        } catch (err: any) {
          return res.json({success: false, error: err}).status(500);
        }
       // ...
      default:
        // This should never trigger because of the first check
        return res.json({error: "Method not found"}).status(422);
    }
  });
    //...
  });


  app.listen(port, () => {
    console.info(`Listening on port ${port}`);
  });
};

export default initServer;

I'm using rollup to bundle the code Nodemon to run. I'm using Node.js version v22.2.0 throughout the project.


Solution

  • You use the wrong order for method json and status, as Express suggests, use res.status(status).json(obj) for sending a json result.

    If you view the code for json method, you can see this:

    res.json = function json(obj) {
      var val = obj;
    
      // allow status / body
      if (arguments.length === 2) {
        // res.json(body, status) backwards compat
        if (typeof arguments[1] === 'number') {
          deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
          this.statusCode = arguments[1];
        } else {
          deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
          this.statusCode = arguments[0];
          val = arguments[1];
        }
      }
    
      // settings
      var app = this.app;
      var escape = app.get('json escape')
      var replacer = app.get('json replacer');
      var spaces = app.get('json spaces');
      var body = stringify(val, replacer, spaces, escape)
    
      // content-type
      if (!this.get('Content-Type')) {
        this.set('Content-Type', 'application/json');
      }
    
      return this.send(body);
    };
    

    Watch out for the deprecate statement. deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');

    For your case, you use json before status,which json object send back to client before status method is called, at that point, statusCode is not assigned, return 200 for default.

    so you can change your code with the following 2 ways:

    • use res.status(301).json(obj), which is recommended.
    • use res.json(301, obj) or res.json(obj, 301), which is deprecated。