Skip to main content
Version: Next

Views

Views are a great solution for abstraction. PostGraphile supports reading from and writing to views; however PostgreSQL lacks the powerful introspection capabilities on views that it has on tables, so we cannot easily automatically infer the relations. However, you can use our "smart tags" functionality to add constraints to views which will make them a lot more table-like (giving them a primary key so you can get a nodeId and maybe CRUD mutations; adding foreign key references between views and other views or tables; setting columns as non-null).

Abstract Business Logic

We can prepare certain queries in advance and expose the results through GraphQL. For example, say we want just the Comedy films from our films table, we can create a view that contains this specific film type.

CREATE TABLE app_public.films (
id serial PRIMARY KEY,
name text,
release_year int,
kind text
);

CREATE VIEW comedies AS
SELECT *
FROM app_public.films
WHERE kind = 'Comedy';

And query this view as if it were a normal table:

{
comedies(first: 20) {
name
releaseYear
}
}

Flatten joined tables

Views enable you to expose a simple "flattened" object built from multiple tables.

CREATE TABLE app_public.person (
id serial PRIMARY KEY
);

CREATE TABLE app_public.address (
person_id int PRIMARY KEY REFERENCES app_public.person,
country text,
street text,
);

CREATE VIEW person_view AS
SELECT person.id, address.country, address.street
FROM app_public.person person
INNER JOIN app_public.address
ON person.id = address.person_id;

The GraphQL query using this view is flatter than the query using the underlying tables:

query Before {
person {
id
address {
country
street
}
}
}

query After {
personView {
id
country
street
}
}
note

You can use smart tags to change the GraphQL field name.

Authorization

Authorization can be enforced using views as well, for example, exposing some data only to authenticated users:

CREATE TABLE app_public.person (
id serial PRIMARY KEY
);

CREATE TABLE app_public.personal_data (
id serial PRIMARY KEY,
secret1 text,
secret2 int,
person_id references app_public.person (id)
);

CREATE VIEW personal_data_view
WITH (security_barrier, check_option = 'cascaded')
AS
SELECT personal_data.*
FROM app_public.personal_data personal_data
WHERE person_id = current_user_id()

(current_user_id() here is a function that might return something like nullif(current_setting('jwt.claims.user_id', true), '')::int)

API Layer

Using views, one can create an access layer that will remain consistent even while making changes to the underlying tables - for example when splitting tables or combining them. Note that simple name changes can be solved using smart tags without the need for views. Much of what can be achieved with views can also be achieved with functions or plugins, so consider those options too.