Create your next Production Ready GraphQL API in minutes.
By: Abhishek Kapoor
Tags: Postgres, Productivity, Software Development, Microservices, Programming

Photo by Possessed Photography on Unsplash

Create your next Production Ready GraphQL API in minutes.

What if I tell you that you can create a fully functioning GraphQL server that uses Postgres for persistence in just a matter of minutes? If you change table schema in the future, your GraphQL API will automatically detect that change and change itself automatically? Would you believe me? It does sound like Magic, right? Well, let me assure you there is no magic involved here. We will use some libraries which will look like some magic. Excited? Well, let's see which tools we will be using today.

Postgres: Our loved Elephant.

Photo by Geran de Klerk on Unsplash

Postgres have many features when I said many, oh boy, there are seriously many. In fact, Postgres has more features than any other open-source database. Did you know Postgres can handle the authorization part for your application also? And not only this, but you can also write your business code in Postgres. Don't believe me? Today, we will do the same thing, and our business code will be in Postgres only.

Postgraphile: The wizard.

Photo by Artem Maltsev on Unsplash

PostGraphile will do all the heavy lifting for us today, and it will connect with Postgres DB and expose our database as a graphQL API. On the Official website of Postgtaphile, they have defined it as:

PostGraphile automatically detects tables, columns, indexes, relationships, views, types, functions, comments, and more — providing a GraphQL server that is highly intelligent about your data, and that automatically updates itself without restarting when you modify your database.

Cool right? PostGraphile is an open-source project, and kudos to them for creating such an efficient library. If you are going to use Postgraphile in production, you can also check their pro plan, unlock some new features of Postgraphile.

You can find Instruction to set up Postgraphile, and Postgres can be found here.

Let's, first of all, create two schemas.

create schema medium_example_public;

create schema medium_example_private;

commit;

you can create as many schemas as you want but in this tutorial, I will be using 2 schemas. I will create all tables in the public schema and the private schema, there will be tables related to authentication. If you are going to use Postgraphile in production also, I recommend defining table structure in the different schema and then exposing those tables in the public schema with views.

Now, let’s create 1 table in the public schema to contain the author’s data.

create table medium_example_public.author (

id serial primary key,

name text not null check (char_length(name) < 80),

about text,

created_at timestamp default now()

);



Now every writer can write articles on some topic, so let's first create an enum with a different topic name to use later in the post’s table.

create type medium_example_public.post_topic as enum (

'Programming',

'Gaming',

'Music',

'Culture'

);

Now, let’s create 1 more table to store all posts.

create table medium_example_public.post (

id serial primary key,

author_id integer not null references

medium_example_public.author(id),

title text not null check (char_length(title) < 280),

body text,

topic medium_example_public.post_topic,

created_at timestamp default now()

);

Till now we have created 2 tables, we can also add comments on tables as well as on columns to autogenerated documentation. So let’s do it also

comment on table medium_example_public.author is 'A user of the medium.';

comment on column medium_example_public.author.id is 'The primary unique identifier for the person.';

comment on column medium_example_public.author.name is 'The person’s name.';

comment on column medium_example_public.author.about is 'A short description about the user, written by the user.';

comment on column medium_example_public.author.created_at is 'The time this person was created.'; comment on table medium_example_public.post is 'A forum post written by a author.';

comment on column medium_example_public.post.id is 'The primary key for the post.';

comment on column medium_example_public.post.title is 'The title written by the user.';

comment on column medium_example_public.post.author_id is 'The id of the author.';

comment on column medium_example_public.post.topic is 'The topic this has been posted in.';

comment on column medium_example_public.post.body is 'The main body text of our post.';

comment on column medium_example_public.post.created_at is 'The time this post was created.';

You have now everything to run GraphQL’s API already. Let’s start PostGraphile

postgraphile -c postgres://postgres:123456@127.0.0.1/medium --schema medium_example_public

We can already run some GraphQL’s Query, let’s assume for the author’s homepage we needed to fetch the author's detail and all posts written by the author. It can easily be done like below.

It’s unbelievable that we already have some basic GraphQL queries running, right? If you want to support some other GraphQL’s according to your business, that can also be achieved effortlessly. Let’s say you want to search a string in the post's body and title. We can write functions in Postgres to support business operations. For example, we can write the below function to search through posts.

create function medium_example_public.search_posts(search text)

returns setof medium_example_public.post as $$

select post.*

from medium_example_public.post as post

where position(search in post.title) > 0 or position(search in post.body) > 0

$$ language sql stable; comment on function medium_example_public.search_posts(text) is 'Returns posts containing a given search term.';

You can see how simple it is. Now let’s test this also.

How cool it is, that you can do so much in so little time? and not only this, but you can also use pagination with queries if you want.

You may be wondering that maybe it is the fastest way to develop GraphQL API’s but where is security? Well, you are right, we cannot deploy any Application without security.

For the security of our API, we are going to use the JWT token. You will need to define a datatype that PostGraphile will use to generate the JWT token. We need our JWT token to contain information about the account id of the author, role, and expiration date of the token. So we will create a type in Postgres like below.

create type medium_example_public .jwt_token as (

role text,

account_id integer,

exp bigint

);

PostGraphile will take this type and make a JWT token for us. We will also need to store the email and password of the user so that once the user is authenticated we can send him a JWT token in output. Let’s create a table for it in our private schema.

create table medium_example_private.account (

account_id integer primary key references medium_example_public.author(id) on delete cascade,

email text not null unique check (email ~* '^.+@.+\..+$'),

password text not null

);

comment on table medium_example_private.account is 'login details about authors account.';

comment on column medium_example_private.account.account_id is 'Author Id';

comment on column medium_example_private.account.email is 'The email address of the author.';

comment on column medium_example_private.account.password is 'password of author, stored as hash.'

In the account table, we are storing email id, hash encrypted password, and author’s id. Now, let's create a function that will take email and password in input and will return the JWT token in output.

create function medium_example_public.authenticate(email text,

password text) returns medium_example_public.jwt_token as $$

declare

account medium_example_private.account;

begin

select a.* into account from medium_example_private.account as a

where a.email = $1;

if account.password = crypt(password, account.password) then

return ('medium_example_author', account.account_id, extract(epoch from (now() + interval '1 days')))::medium_example_public.jwt_token;

else

return null;

end if;

end;

$$ language plpgsql strict security definer; comment on function medium_example_public.authenticate(text, text) is 'Create a JWT token with 1 day of validity';

If you know basic SQL you already know what we are doing in the above function. We are trying to authenticate the user, if the email and password are correct we send JWT otherwise null is returned. In case you are wondering what is Crypt? well, it is a function in Postgres that will return a hash of the string.

Are you Bored reading so many SQLs? just bear with me for 2 more SQLs. Let’s quickly write a function to register a user. This function will insert data in the account table as well as the author table.

create function medium_example_public.register_author(

name text,

email text,

password text

) returns medium_example_public.author as $$

declare

author medium_example_public.author;

begin

insert into medium_example_public.author (name) values

(name)

returning * into author ;

insert into medium_example_private.account (account_id, email, password) values

(author.id, email, crypt(password, gen_salt('bf')));

return author;

end;

$$ language plpgsql strict security definer;

comment on function medium_example_public.register_author(text, text, text, text) is 'Register an author';

Now, we have a way to register users, a way to log in users, but hey! I think we are still missing something. I know, we are missing roles, every user should have different roles like a user should be able to see and search posts without having an account, and every user can only access its personal data. For this we are going to use the roles of Postgres, lets create 2 roles:

create role medium_example_anonymous;

create role medium_example_author;

we have now an anonymous role and an author’s role. Now let’s apply for some grants on these tables based on our business. Our anonymous user should be able to only view posts, search for posts, register as an author and view other authors. Once the user is logged in he can modify posts, create new posts or even delete posts.

alter default privileges revoke execute on functions from public;

grant usage on schema medium_example_public to medium_example_anonymous, medium_example_author;

grant select on table medium_example_public.author to medium_example_anonymous, medium_example_author;

grant update, delete on table medium_example_public.author to medium_example_author; grant select on table medium_example_public.post to medium_example_anonymous, medium_example_author;

grant insert, update, delete on table medium_example_public.post to medium_example_author;

grant usage on sequence medium_example_public.post_id_seq to medium_example_author;

grant execute on function medium_example_public.search_posts(text) to medium_example_anonymous, medium_example_author;

grant execute on function medium_example_public.authenticate(text, text) to medium_example_anonymous, medium_example_author;

grant execute on function medium_example_public.register_author(text, text, text) to medium_example_anonymous;

By default, functions in Postgres can be executed from the public, in the 1st line above, we are restricting functions to be executed by the public. Then we are giving grants of table and functions based on our requirements. Now let’s try to register as a user.

Very good our user has been registered successfully. Now let’s try to get a JWT token with this user.

As you can see we also got the JWT token successfully. Now let's create a post with this token.

Right now there is one issue, we are only verifying if a user is a registered user of our website which will mean one user can post on behalf of other users also. This is not good, we will definitely want to change that. Postgres also supports row-level locks. So we are going to use a row-level lock and make sure our users can post only for themselves.

alter table medium_example_public.author enable row level security;

alter table medium_example_public.post enable row level security; create policy select_author on medium_example_public.author for select using (true);

create policy select_post on medium_example_public.post for select

using (true);

create policy update_person on medium_example_public.author for update to forum_example_person using (id = nullif(current_setting('jwt.claims.account_id', true), '')::integer);

create policy delete_person on medium_example_public.author for delete to forum_example_person using (id = nullif(current_setting('jwt.claims.account_id', true), '')::integer);

create policy insert_post on medium_example_public.author for insert to forum_example_person with check (author_id = nullif(current_setting('jwt.claims.account_id', true), '')::integer);

create policy update_post on medium_example_public.author for update to forum_example_person using (author_id = nullif(current_setting('jwt.claims.account_id', true), '')::integer);

create policy delete_post on medium_example_public.post for delete to forum_example_person using (author_id = nullif(current_setting('jwt.claims.person_id', true), '')::integer);

Now let’s try to create a post with the author id of some other user

As you can see we are not allowed to do that. Well, that was it folks for today. If you read it up till here, I really want to thank you.

Conclusion

When you are going to start a startup it is very much important that you can implement your idea as soon as possible. In such cases, tools like Postgraphile and Postgres can come handy. Most of the time during development we waste our time recreating features that are already there like Authorization in Postgres. Just imagine if you had to do the same in Spring boot, you will be writing a lot of code for authentication, role-based access which you even don’t really need to. I highly recommend you should give PostGraphile a try once.

Resources