skip to Main Content

I know, it sounds really wierd, but it’s true.
I was sitting and making an API on node.js, but when I began to test it, I was surprised to find out that nearly in beginning of query treatment when the first res.status().send() reached, VS Code drop a "Cannot set headers after they are sent to the client" error.
How I realize that database query is guilty? I used "res.finished" property to check out when it "sent to client" and discovered that res.finished changes from "false" to "true" exactly after db query.

Who knows what it can be? I had done the same API thing with MySQL database and it went nice, but now I’m using PostgreSQL, so things start to happen.

I export "PostgreSQL manager" class from typescript file

PostgreSQL_Manager.ts:

module.exports = {
    PostgreSQL_db_manager    
}

Import and initialize it in index.ts:

index.ts

const PostgreSQL_mngr = require('./PostgreSQL_Manager.ts').PostgreSQL_db_manager;
const db = new PostgreSQL_mngr;

And then, if I comment the statement with query to database, res.finished stay false (I tried it with readRows (SELECT) and with createRows(INSERT INTO)):

Piece of index.ts code: 

console.log('res.finished : ', res.finished);
//let nickval : any = await db.readRows('users', 'nickname', `nickname = '${nickname}'`);
//await db.createRows('test', '(color, odor, taste, quantity)', '('meaningless', 'absent', 'sadness', 0)');
console.log('res.finished : ', res.finished);

Piece of Terminal:

res.finished :  false
res.finished :  false

But when I uncomment database query, it becomes this:

Piece of index.ts code: 

console.log('res.finished : ', res.finished);
//let nickval : any = await db.readRows('users', 'nickname', `nickname = '${nickname}'`);
await db.createRows('test', '(color, odor, taste, quantity)', '('meaningless', 'absent', 'sadness', 0)');
console.log('res.finished : ', res.finished);

Piece of Terminal:

res.finished :  false
Postgres: Rows were created...
res.finished :  true

Code of db.createRows in postgres manager class looks like this:

public async createRows(table : string, columns: string | string[], values: string | string[]) : Promise<void> {
        let createPromise = new Promise<void> ((resolve, reject) => {
            this.db.query(`INSERT INTO ${table} ${columns} VALUES ${values};`, (err) => {
                if(err) throw err;
                console.log('Postgres: Rows were created...');
                resolve();
            });
        });
        await createPromise;        
    }

Edit 1:
There is error occurs (This function called from app.post, nickname, email and password has string values):

async function validationUsers (res : any, email : string = undefined, password : string = undefined, nickname : string = undefined) : Promise<boolean> {
    console.log('f:validationUsers email : ', email);
    console.log('f:validationUsers password : ', password);
    console.log('f:validationUsers : nickname', nickname);
    //validation: nickname and email
    if(nickname) {
        console.log('res.finished : ', res.finished);
        //let nickval : any = await db.readRows('users', 'nickname', `nickname = '${nickname}'`);
        await db.createRows('test', '(color, odor, taste, quantity)', '('meaningless', 'absent', 'sadness', 0)');
        console.log('res.finished : ', res.finished);
        /* if(nickval[0] !== undefined) {
            console.log('frofkofro');
            res.status(410).send('Nickname already exists');
            res.end();
            return false;
        } */
    }
    //validation: email
    if(email) {
        let emailval : any = await db.readRows('users', 'email', `email = '${email}'`);
        console.log('f:validationUsers if(email) emailval[0] : ', emailval[0]);
        if(emailval[0] !== undefined) {
            console.log("?00");
            res.send('Email already exists');
            res.end();
            return false;
        }
    }
    //validation: password
    if(password) {
        let passwordval = /^(?=.*d)(?=.*[a-z])(?=.*[A-Z]).{8,100}$/;
        if(!password.match(passwordval)) {
            console.log('password BAM!!!');
            res.status(412).send('Password does not match the criteria'); <-- **THIS STRING**
            console.log('password BOOM!!!');
            //res.end();
            return false;
        }
    }
    console.log('End of f:validationUsers');
    return true;
}

Edit 2:
Can it be some problem with pool.query or pool connection to database from "pg" library for PostgreSQL? Or maybe problem with ts-node compiler?

So, I really don’t understand what’s going on.
I don’t know if it’s important, but I use ts-node for compile and render typescript

Edit 3:
OKAY, so I started in new ts file new server with the same 5000 port and run THIS:

app1.get('/db', async (req : any, res : any) => {
  console.log('res.finished : ', res.finished);
  await db1.createRows('test', '(color, odor, taste, quantity)', '('meaningless', 'absent', 'sadness', 0)');
  console.log('res.finished : ', res.finished);
  res.status(200).send('All is fine this is send');
  res.end();
});

And result in console:

Connected to database as pool successfully...
Server started on port 5000
res.finished :  false
Postgres: Rows were created...
res.finished :  false

And POSTMAN received res.send(). wtf??????

3

Answers


  1. Chosen as BEST ANSWER

    Okay, it's really strange and wierd. I have middleware function app.use and it looked something like that:

    app.use(async function (req : any, res : any, next : any) {
       console.log(smthng);
       get('header') stuff;
       if (cond) {
          // this is not executed because condition was false in all my situation
       } else {
          // this is executed in all cases of this thread
          req.name = undefined;
          next();
       }
       next()
    })
    

    As you see, in the end of middleware function was next(). So, I removed JUST THIS NEXT AND:

    
    Terminal:
    
    res.finished :  false
    Postgres: Rows were read...
    []
    res.finished :  false
    res.finished :  false
    Postgres: Rows were read...
    []
    f:validationUsers if(email) emailval[0] :  undefined
    res.finished :  false
    password BAM!!!
    password BOOM!!!
    

    I do not know what happened, I think this is deepsea shizophrenic hyperfluid flows under complier with some crossroad between db promise, middleware and res.send()


  2. The error ‘Headers already sent…’ happens when your PostgreSQL code sends multiple results using res.send().

    I assume the res.send() part is within PostgreSQL manager, which looks like a tested and true library – so what is happening to make it send two answers to one query?

    I have no experience with Typescript and pSQL, but I have worked with pSQL and remember hitting this same snag years ago.

    PostgreSQL supported (and I imagine it still supports) multiple query mode, such as, UPDATE b SET a=2 WHERE c; SELECT a FROM b. Those are two statements, and the reason why some exploits can even work.

    And, just like it happened to me once, even if the second one has zero length and apparently is not even a query, in your code

     `INSERT INTO ${table} ${columns} VALUES ${values};`
    

    your PostgreSQL Manager just might think that there are two statements.

    So, try removing that apparently harmless ‘;’ at the end and see whether it solves the problem. I wasn’t using your libraries, but for me, that did it.

    Login or Signup to reply.
  3. I would say that the error is comming from here :

            res.send('Email already exists');
            res.end();
    

    Indeed if you node’s doc reads :

    The res.end() function is used to end the response process. This method actually comes from the Node core, specifically the response.end() method of HTTP.ServerResponse. Use to quickly end the response without any data.

    given that you already responsed 'Email already exists' express.js, you recieve the error message Cannot set headers after they are sent to the client (it just means you have already sent a response)

    I think just removing res.end(); would fix your issue.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search