Sailscasts is now live 🔥. Get 50% off a Sailscasts Pro subscription now!
The Sailscasts Blog

Understanding Waterline projections

Projections make for faster performance and better security when sending back records to a client by allowing you to select or omit fields to be returned.

Returning all the fields in a record may be suitable some of the time but most of the time, you may want to be intentional with what's being returned from a record. Here are some reasons you may want to do this:

  • Remove sensitive informations like password and SSNs
  • Make faster and more performant Waterline queries by returning a subset of the fields of a record.
  • Making sure only the fields you intended get sent to the client.

In other to achieve this behavior of selecting or omitting fields in a Waterline query, Waterline provides us with Projections.

What are projections

Projections are mechanisms for controlling what fields gets returned from a Waterline query. Under the hood, Waterline is always adding the default values of these projections to your query. Let's look at what these projections are:

Select

The select projection allows you to select what fields to be returned in a Waterline query. By default when you do something like:

await User.find()

Waterline expands the above query to the below form:

await User.find({select: ['*']})

Note: ['*'] is the default value for the select projection.

If you are familiar with SQL statements you will notice that the select projection is similar to writing the following SQL statement:

SELECT * FROM users

select usage

To use the select projection, you either pass it as a property in your Waterline query criteria or you call the .select method on your Waterline query.

await User.find({select: [] })

Or

await User.find().select([])

The select projection takes an array of strings where each string is the name of the field you want to include in the query. So let's say we want the users' email, first name and age, we will write:

await User.find({ select: ['email', 'firstName', 'age'] })

Or

await User.find().select(['email', 'firstName', 'age'])

Omit

The omit projection is the reverse of the select projection. Instead of specifying the fields you want to return using the select projection, with the omit projection you specify the fields you don't want to return.

This is useful as you might want to return 20 fields and omit 3, it's much more efficient in this scenario to use omit to specify the 3 fields you don't want to return as opposed to typing out the 20 fields you want to return.

omit usage

Waterline defaults the omit projection to an empy array - this means the query won't omit any fields. So when you do:

await User.find()

Waterline expands to

await User.find({omit: []})

Note: [] is the default value for the omit projection.

Like its select, counterpart you can either pass omit as a property to your query or as a Waterline method. So you can do:

await User.find({ omit: [] })

Or

await User.find().omit([])

So if you want to omit the password and ssn fields from your users records when returning them to a client, you can do:

await User.find({ omit: ['password', 'ssn'] })

Or

await User.find().omit(['password', 'ssn'])

Waterline assumptions

Waterline holds some assumptions about the usage of omit/select. let's look at them.

Always return the primary key

When using projections, you should expect the primary key for the records to always be included. Trying to omit the primary key in a Waterline query is invalid and Waterline will complain. So the below is invalid:

// invalid usage of omit - can't omit the primary key
await User.find({ omit: ['id'] })

Also note that the primary key will always be included in your select projections so you don't need to specify it.

Use select or omit but never both

You can't use omit and select in the same query. Waterline assumes you must either use select or omit but never both so it will throw an error if you try to do so. So the below is invalid in Waterline:

// wrong usage of select/omit
await User.find({ select: ['email'], omit: ['password'] })

Usage with where clause shorthand

Waterline allows you to do a query using this shorthand syntax:

await User.find({ gender: 'female' })

This will expand to the where clause like so:

await User.find({ where: { gender: 'female' } })

The shorthand method is really convenient for fewer key strokes however if you want to use any of the projections as a query criteria property you will always need to use the where clause for specifying query criteria. So the below will be invalid in Waterline:

// invalid syntax
await User.find({ gender: 'female', select: ['age'] })

Instead you should write:

// correct syntax
await User.find({ where: { gender: 'female' }, select: ['age'] })

However you can opt in on using the methods version of the projections in order to keep using the query shorthand like so:

await User.find({ gender: 'female' }).select(['age'])

Conclusion

Projections are handy for having the flexibility of defining what fields in your database records get sent back to the client. This article explains the two projections in Waterline and their usage in a Sails application.

The Sailcasts Blog Newsletter

Get notified 🔔 when new articles are published on the Sailcasts Blog.