sequelize: Using underscored: true still returns attributes in camelCase

What are you doing?

I have a project that uses Sequelize as ORM. I need the attribute names returned in snake_case, but although I’ve specified underscored: true in my model definition, the attributes are returned in camelCase instead. (The DB tables are in snake_case, as expected).

I’m also using Sequelize 4.x in a few other projects and it doesn’t affect it afaik.

Can be the same reason as here: https://github.com/sequelize/sequelize/issues/10581 (but it was closed because of not proper issue format).

I can provide more details if needed.

Sequelize config:

const Sequelize = require('sequelize');

const getSequelize = () => new Sequelize(config.postgres.database, config.postgres.username, config.postgres.password, {
    host: config.postgres.host,
    port: config.postgres.port,
    dialect: 'postgres',
    logging: sql => logger.debug(sql)
});

let sequelize = getSequelize();

exports.sequelize = sequelize;
exports.Sequelize = Sequelize;

Model definition

const { Sequelize, sequelize } = require('../lib/sequelize');

const Code = sequelize.define('code', {
    value: {
        type: Sequelize.STRING,
        allowNull: false,
        defaultValue: '',
        validate: {
            notEmpty: { msg: 'Code should be set.' },
        },
    },
    integration_id: {
        type: Sequelize.INTEGER,
        allowNull: false,
        defaultValue: '',
        validate: {
            notEmpty: { msg: 'Integration ID should be set.' },
        },
    },
    claimed_by: {
        type: Sequelize.INTEGER
    }
}, { underscored: true, tableName: 'codes' });

module.exports = Code;

Endpoint using this model (I have 2 models but I provide only one for the case of simplicity):

exports.getMyCodes = async (req, res) => {
    const myCodes = await Code.findAll({
        where: {
            claimed_by: req.user.id
        },
        include: [Integration],
        order: [
            ['updated_at', 'DESC'],
        ]
    });

    return res.json(myCodes);
};

What do you expect to happen?

Everything returned in snake_case

What is actually happening?

The server response (note the integrationId, createdAt and updatedAt are in camelCase):

[{
    "id": 7,
    "value": "2",
    "integration_id": 2,
    "claimed_by": 1,
    "createdAt": "2019-04-28T19:14:51.641Z",
    "updatedAt": "2019-04-28T19:16:24.259Z",
    "integrationId": 2,
    "integration": {
        "id": 2,
        "name": "Help",
        "code": "flixbus",
        "quota_period": "month",
        "quota_amount": 3,
        "description": "**I need somebody**",
        "createdAt": "2019-04-28T19:14:15.164Z",
        "updatedAt": "2019-04-28T19:14:15.164Z"
    }
}, {
    "id": 6,
    "value": "1",
    "integration_id": 2,
    "claimed_by": 1,
    "createdAt": "2019-04-28T19:14:51.641Z",
    "updatedAt": "2019-04-28T19:14:55.130Z",
    "integrationId": 2,
    "integration": {
        "id": 2,
        "name": "Help",
        "code": "flixbus",
        "quota_period": "month",
        "quota_amount": 3,
        "description": "**I need somebody**",
        "createdAt": "2019-04-28T19:14:15.164Z",
        "updatedAt": "2019-04-28T19:14:15.164Z"
    }
}, {
    "id": 1,
    "value": "one",
    "integration_id": 1,
    "claimed_by": 1,
    "createdAt": "2019-04-28T19:01:07.660Z",
    "updatedAt": "2019-04-28T19:12:03.643Z",
    "integrationId": 1,
    "integration": {
        "id": 1,
        "name": "test",
        "code": "test",
        "quota_period": "month",
        "quota_amount": 1,
        "description": "test",
        "createdAt": "2019-04-28T19:01:01.724Z",
        "updatedAt": "2019-04-28T19:01:01.724Z"
    }
}]

It only affects Sequelize 5.x, not 4.x

I’ve tried to downgrade to sequelize and sequelize-cli 4.x, and it works as expected:

$ npm ls | grep sequelize
├─┬ sequelize@4.43.2
├─┬ sequelize-cli@4.1.1

and the server response (with the exact same example above, only thing changed was downgrading sequelize and sequelize-cli packages) looks like this:

[{
    "id": 8,
    "value": "3",
    "integration_id": 2,
    "claimed_by": 1,
    "created_at": "2019-04-28T19:14:51.641Z",
    "updated_at": "2019-04-29T16:52:31.198Z",
    "integration": {
        "id": 2,
        "name": "Help",
        "code": "flixbus",
        "quota_period": "month",
        "quota_amount": 3,
        "description": "**I need somebody**",
        "created_at": "2019-04-28T19:14:15.164Z",
        "updated_at": "2019-04-28T19:14:15.164Z"
    }
}]

Environment

Dialect: postgres, also can be that it affects all of them. Dialect library version: pg 7.10.0 Database version: psql 10 Sequelize version: 5.7.6 Node Version: 10.2.1 OS: MacOS Mojave 10.14.4 (x64) Tested with latest release: yes, with 5.7.6 (latest NPM release afaik)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 35
  • Comments: 56 (4 by maintainers)

Most upvoted comments

Hello, I was having the same problem as @twistedrc1017 with the timestamps created_ at and updated_at.
The timestamps fields were both defined as snake case in the model:

image

And both undescored and undesrcoredAll were set to true in my database configuration file:

image

But I was still getting cameCase timestamps in my responses:

image

image

To get my timestamps to snake case I set the following values to createdAt:‘created_at’ and the updatedAt:‘updated_at’ in my database config (as said in the docs If you want sequelize to handle timestamps, but only want some of them, or want your timestamps to be called something else, you can override each column individually ):

image

Then I got my timestamps in snake case:

image

image

All other fields were already being converted from camelCase to snake case and coming back in response as snake case. The only fields that were coming in response as camelCase were the two timestamps mentioned, the configuration helped solve my issue, I hope it helps you too! 😬

My grandchildren have already gone to school. But this error has still not fixed.

I’ve just upgraded from version 4 to 5.21.5. It looks like underscored: true no longer honors foreign key conventions. For example, I had to explicitly add foreignKey to make this work the way it used to:

Feature.belongsToMany(models.permission, {
	through: models.feature_permission,
	foreignKey: "feature_id"
});

If I don’t call it out it becomes featureId.

I only recently upgraded to 5.x, so it is definitely a bug in V5 as you’ve just proven by downgrading. Hope this can be fixed ASAP.

It’s a bug and It should be fixed. As it’s not a good idea to use 2 different cases for the field names.

i am also experiencing this on v5.10.1 image

it seems fine when i look at my db with pgAdmin though: image

Version 6.x seems to be affected as well.

Almost same issue here: I use underscored: true & timestamps: true, and I expect created_at, updated_at fields in snake_case, as it was on sequelize@4. But after upgrading to sequelize@5 fields becomes createdAt and updatedAt

@miladmeidanshahi Thanks!

For clarity’s sake, in the following text when I say:

  • attribute, I mean the JavaScript name,
  • column, I mean the Database name.

So by default Sequelize will generate camelCase attributes and snake_case column (actually that second part is not the default yet) names because we consider it to be the standard naming conventions of JS & SQL respectively.

The underscored option is only used to generate snake_case column names. As others have found out in this thread, it has no impact on attribute names.

For timestamp attributes, the solution is simple enough. You can configure it per-model using the 3 timestamp options:

UserModel.init({ /* attributes */ }, {
  createdAt: 'created_at',
  updatedAt: 'updated_at',
  deletedAt: 'deleted_at',
})

Or configure them for every model by configuring the Sequelize instance itself:

new Sequelize({
  define: {
    createdAt: 'created_at',
    updatedAt: 'updated_at',
    deletedAt: 'deleted_at',
  }
});

For foreign keys, it’s trickier. There is a way to configure it per-model but there is no system wide configuration option.

UserModel.hasMany(Film, {
  as: 'author',
  foreignKey: {
    name: 'author_id', // the JavaScript attribute name
    field: 'author_id', // the column name
  },
})

To automatically generate a snake_case foreign key attribute names would require the return of an option like underscoredAll (but with a better name).

If a way to tell sequelize to generate attributes in snake_case is the desired solution, I’ll keep this closed in favor of https://github.com/sequelize/sequelize/issues/11225 as it seems to be the relevant thread.

Let me know what you think 😃

Edit:

I also think the following two RFC are relevant, at least somewhat:

Thanks @dzvid your configuration worked for me:

image

https://sequelize.org/master/manual/naming-strategies.html

Note that in both cases the fields are still camelCase in the JavaScript side; this option only changes how these fields are mapped to the database itself. The field option of every attribute is set to their snake_case version, but the attribute itself remains camelCase.

I’ve also noticed this issue (6.3.5). Any updates?

I have the same or similar issue with sequelize v5 and postgres. My schema is smaller so I’m seing this duplication only on xxxxx_id fields (and all of them are relations). Here are few examples:

Executing (default): SELECT "users"."id", "users"."username", "users"."password_hash", "users"."account_id", "users"."account_id" AS "accountId", "account"."id" AS "account.id", "account"."name" AS "account.name", "account"."token_balance" AS "account.token_balance" FROM "users" AS "users" LEFT OUTER JOIN "account" AS "account" ON "users"."account_id" = "account"."id" WHERE "users"."account_id" = 1 LIMIT 1;

Executing (default): SELECT "id", "username", "password_hash", "account_id", "account_id" AS "accountId" FROM "users" AS "users" WHERE "users"."id" = 1;

this one shows that the ‘camel-ization’ happens only on the root entity table (request_log) while the JOIN-ed table is not affected (see report_request.account_id) Executing (default): SELECT "request_log"."id", "request_log"."report_request_id", "request_log"."report_request_id" AS "reportRequestId", "request_log"."ts", "request_log"."user_id", "report_request"."id" AS "report_request.id", "report_request"."account_id" AS "report_request.account_id", "report_request"."was_billed" AS "report_request.was_billed", "report_request"."exec_ts" AS "report_request.exec_ts", "report_request"."ttl_ts" AS "report_request.ttl_ts", "report_request"."balance_snapshot" AS "report_request.balance_snapshot" FROM "request_log" AS "request_log" LEFT OUTER JOIN "report_request" AS "report_request" ON "request_log"."report_request_id" = "report_request"."id" WHERE "request_log"."user_id" = '1' ORDER BY "request_log"."ts" DESC LIMIT '10' OFFSET '0';

I’ve just upgraded from version 4 to 5.21.5. It looks like underscored: true no longer honors foreign key conventions. For example, I had to explicitly add foreignKey to make this work the way it used to:

Feature.belongsToMany(models.permission, {
	through: models.feature_permission,
	foreignKey: "feature_id"
});

If I don’t call it out it becomes featureId.

Great, what you said is the root cause (underscored: true no longer honors foreign key conventions). You helped me, thanks. But, can anyone solve this problem?

I think it’s worth noting that based on the current documentation, this is not a bug at all but instead a fundamental change to how it works:

https://sequelize.org/master/manual/naming-strategies.html

Note that in both cases the fields are still camelCase in the JavaScript side; this option only changes how these fields are mapped to the database itself. The field option of every attribute is set to their snake_case version, but the attribute itself remains camelCase.

Which is great if I’m starting a new project from scratch. I think fully camelCased Javascript field names is a great convention; but it causes real problems when upgrading older Sequelize projects where we expect our Javascript attributes in snake_case. As mentioned above, this can be mitigated by explicitly setting any autogenerated fields like createdAt: 'created_at'.

But I wouldn’t expect there to ever be a “fix” since it now matches up with their documentation.

It’s just hilarious. Fortunately, only after 20 mins of struggling I’ve realized that’s this was not my fault. ^6.3.5 confirmed bug.

this error still appear on version 6.3.4 I’ve added underscore: true for each model but still doesn’t work

image

@papb any idea when this bug will be fixed?

option underscored: true affected on database column names, after find quarry still is camel case.

hey guys . it’s still not working . i have create_at in migration and used underscored: true in my models but it’s not working

I’ve recently understood why it’s failing. Each model has an attributes object, where the keys are the camelCase attributes names, and each of these objects has a field field which is a snake_case or camelCase (depending on if underscored is true or false).

Here is where the field field is set based on underscored option:

https://github.com/sequelize/sequelize/blob/30c5ca5e127474f585388a9f26aefc06024fe2ec/lib/model.js#L1134

In the findAll() method where the options object for SQL query is constructed, it uses Object.keys(this.rawAttributes) which will take the names in camelCase disregarding the underscored option, here: https://github.com/sequelize/sequelize/blob/30c5ca5e127474f585388a9f26aefc06024fe2ec/lib/model.js#L1732

If I change it to Object.keys(this.rawAttributes).map(key => this.rawAttributes[key].field) it should work. However it won’t turn includes attributes to snake_case.

I’ll most like make a PR on this either this or next week, if somebody will be faster then me I’d really appreciate it!

Unfortunately I don’t have the time or means to get other dialects setup at the moment.

I am seeing the same problem. My issue seems to be that only association/foreign key variables are not becoming snake_case (same as #10581).