Skip to content

Instantly share code, notes, and snippets.

@sacarino
Created August 24, 2018 02:49
Show Gist options
  • Save sacarino/9295ea09f020eee1b2274cd297cc4f22 to your computer and use it in GitHub Desktop.
Save sacarino/9295ea09f020eee1b2274cd297cc4f22 to your computer and use it in GitHub Desktop.
Cheatsheet for creating LoopbackJS relationships using the PostgreSQL connector

This is my cheatsheet, because the Loopback docs aren't the best and I spent way too long puzzling this stuff out.

Consider this a living document. If it helps you, too... great! If you want to make a suggestion, lmk.

First... the why.

It's a PostgreSQL best-practice to use lowercase names, using snakecase if you need to. ie, my_column_name. You CAN force CamelCase or all uppercase, but it's a really bad idea. Don't.

Also, since I'm using a JSON API client, I've installed the excellent loopback-component-jsonapi to handle the serialization stuff... but that just added additional complexities.

JSON API calls for dasherized property names. When you start with something like my-property-name, Loopback or the PostgreSQL driver (blame doesn't matter) mangles the dasherized property so that it collapses down to mypropertyname by default.

This is bad... especially when you have an existing schema you're working with.

It's worse when you're working with relationships, because Loopback also appends the id suffix by default, so now you have issues unless you have a mypropertynameid column.

An example

Let's say we have a Customer model. I needed endpoints that are dasherized lowercase, so just change the plural to match here.

{ 
  "name": "Customer",
  "plural": "customers",
  "base": "PersistedModel",
   ...
 }

Inside of options.postgresql, you can set a tablename. Loopback will use the Name value by default, but remember PostgreSQL doesn't like Camelcase. You need to override this, unless you use lowercase model names.

(It's a religious preference, but I like my tables to be plurals. Fight me.)

{ 
  ...
  "options": {
    "validateUpsert": true,
    "postgresql": {
      "tableName": "customers"
    }
  }
  ...
}

Back to the properties, use the postgresql.columnName property to map to the correct column name in the db. If it's not a dasherized property name (ie status) then you can ignore the postgresql.columnName bit.

{ 
  ...
  "properties": {
    "is-active": {
      "type": "boolean",
      "default": false,
      "postgresql": {
        "columnName": "is_active"
      }
    }
  }
}

Relationships can be a headache.

Let's say our Customer has People who work there. To do a basic one-many relationship between the models...

{ 
  ...
  "relations": {
    "people": {
      "type": "hasMany",
      "model": "Person",
      "foreignKey": "customer_id"
    }
  },
  ...
}

people is the name of the relationship. Per the JSON API spec, these are a seperate element than the properties that you list above. (Those show up as an attributes element.)

A "gotcha" here is the foreignKey property.

The Loopback docs say it's optional - and it is - but if you leave it out then it adds the id suffix to the name (people) and then looks for that column in your customers table.

This part wasn't clear => I originally thought the foreignKey value pointed to the property of the Person model, so I had the dasherized customer-id property here. That's incorrect. It's literally asking you for the database column name, which is a bit of an antipattern since in the properties you had to define a columnName if you wanted to refer to the db columns under the ORM.

Also, note that the foreignKey property is reused in relationships but it means different things to different type contexts. In a hasMany, it's asking "Which column there maps to the primary key here?"

Final Customer model:

{ 
  "name": "Customer",
  "plural": "customers",
  "base": "PersistedModel",
  "options": {
    "validateUpsert": true,
    "postgresql": {
      "tableName": "customers"
    }
  },
  "properties": {
    "name": {
      "type": "string"
    },
    "is-active": {
      "type": "boolean",
      "default": false,
      "postgresql": {
        "columnName": "is_active"
      }
    }
  },
  "validations": [],
  "relations": {
    "people": {
      "type": "hasMany",
      "model": "Person",
      "foreignKey": "customer_id"
    }
  },
  "acls": [],
  "methods": {}
}

The Person model on the other end of the relationship.

The foreignKey for a belongsTo relationship is asking the opposite question... "Which property here maps to the primary key there?"

Also, if you have properties you don't want exposed (especially if you've inherited a model and don't want/need all those properties for whatever reason) then you can hide them with the hidden element. See below.

{
  "name": "Person",
  "plural": "people",
  "base": "User",
  "idInjection": false,
  "options": {
    "validateUpsert": true,
    "postgresql": {
      "tableName": "people"
    }
  },
  "hidden": [
    "emailVerified",
    "realm",
    "username",
  ],
  "properties": {
    "first-name": {
      "type": "string",
      "postgresql": {
        "columnName": "first_name"
      }
    },
    "last-name": {
      "type": "string",
      "postgresql": {
        "columnName": "last_name"
      }
    },
    "email": {
      "type": "string"
    },
    ...
  },
  "validations": [],
  "relations": {
    "customer": {
      "type": "belongsTo",
      "model": "Customer",
      "foreignKey": "customer_id"
    }
  },
  "acls": [],
  "methods": {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment