Course Thumbnail

LinkedIn GraphQL backend with StepZen and PostgreSQL

Part of:  Full-stack LinkedIn Clone   

In this tutorial, we will build a production-ready backend for the LinkedIn Clone.

We will start with designing and setting up the Relationship Database. Then we will build a GraphQL API using StepZen and query the API from the React Native LinkedIn app. We will also implement an authentication flow using Clerk.

The tech stack:

  • PostgreSQL deployed to Neon.tech
  • StepZen for the GraphQL API
  • Apollo Client to query the API from React Native
  • Clerk.com for User Authentication

This is a powerful stack that is free to get started, and also scalable to meet the requirements of apps of any size.

Video tutorial

This guide was designed to be used together with the video tutorial. Open it in a new tab, and let’s get started.

Asset Bundle

The Asset bundle contains the starter project for the LinkedIn UI and other important assets.

Download Asset Bundle

This tutorial is sponsored by StepZen

StepZen is a GraphQL server with a unique architecture that helps you build APIs fast and with less code. All you have to do is write a few lines of declarative code and let StepZen do the hard work related to building scalable and performant GraphQL APIs.

Sign up for a free StepZen account: https://bit.ly/3OcTJ1U

Run the starter project

If you have followed the first part of this project LinkedIn UI with React Native, and you have followed along, implementing all the screens we’ve built, then you can continue with your project.

For everyone else, or if you want to be sure we are starting from the same point, download the Asset Bundle above, and there is the source code of the UI.

Unzip the LinkedInStarter.zip and open it in Visual Studio Code.

Install dependencies by running npm install.

Start the development server with npm start and run the application on either an emulator or directly on your device using the Expo Go app.

Now you should be able to explore the application we will work with today.

It has a couple of important screens: Home Feed, Post page, Profile page, New post page and Search page. Go ahead and see how they look and think about what data we will need in our backend.

Untitled.png

Database Design

For the Database, we will use PostgreSQL, which is a powerful Relational Database. Let’s start by designing what tables we will need, and what will be the relationship between them.

Untitled.png

Setup the database

There are multiple options to host a PostgreSQL database. You can run it locally on your machine, you can host it on a server or use a managed solution like AWS RDS, or you can use a service that can simplify the whole process.

For this tutorial, I tried multiple solutions, and finally, I landed on Neon.tech. I found it really easy and intuitive to set up and interact with your database, and they also offer a generous free tier.

Sign up and create a new project. Now we have a PostgreSQL database ready.

Untitled.png

Create all the tables

Our database is still empty. Let’s create all the tables we will need in this tutorial using standard SQL queries based on our database design.

Open the SQL Editor page inside the Neon console, and paste the next SQL Query.

SQL
CREATE TABLE Profile (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(255),
position VARCHAR(255),
image VARCHAR(255),
backImage VARCHAR(255),
about TEXT,
authId VARCHAR(255) UNIQUE
);
CREATE TABLE Experience (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
title VARCHAR(255),
companyName VARCHAR(255),
companyImage VARCHAR(255),
userId BIGINT REFERENCES Profile(id) ON DELETE CASCADE
);
CREATE TABLE Post (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
content TEXT,
image VARCHAR(255),
userId BIGINT REFERENCES Profile(id) ON DELETE CASCADE
);
CREATE TABLE Reaction (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
postId BIGINT REFERENCES Post(id) ON DELETE CASCADE,
userId BIGINT REFERENCES Profile(id) ON DELETE CASCADE,
type VARCHAR(255)
);
CREATE TABLE Comment (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
postId BIGINT REFERENCES Post(id) ON DELETE CASCADE,
userId BIGINT REFERENCES Profile(id) ON DELETE CASCADE,
comment TEXT
);

Insert some data

Let’s also insert some data to help us explore further.

Execute the next SQL commands in the SQL Editor.

SQL
-- Insert Profile
INSERT INTO Profile (id, name, position, image, backImage, about) VALUES
(1, 'Jeff Bezon', 'Intern @Amazon', 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/avatars/jeff.jpeg', NULL, NULL),
(2, 'Vadim Savin', 'Founder at notJust.dev', 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/avatars/vadim.png', 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/images/2.jpg', 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry s standard dummy text ever since the 1500s, when an unknown printer took a galley'),
(3, 'Peter Johnson', 'CTO at Facebook', 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/avatars/zuck.jpeg', NULL, NULL),
(4, 'StepZen, an IBM Company', 'GraphQL server with unique architecture', 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/avatars/stepzen.jpeg', NULL, NULL);
-- Insert Post
INSERT INTO Post (id, content, image, userId) VALUES
(2, 'What does notJust Developer mean to you?', 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/pinterest/0.jpeg', 2),
(4, 'Become a GraphQL Ninja with StepZen 🚀', 'https://notjustdev-dummy.s3.us-east-2.amazonaws.com/images/stepzen-stickers.png', 4),
(1, 'Exciting news! Just landed an internship at Amazon. I figured it''s about time I try out this whole e-commerce thing. 😄 #NewBeginnings #FromCEOtoIntern', NULL, 1),
(3, 'Join me for a talk on scaling technology in startups', NULL, 3);
-- Insert Reaction (likes)
INSERT INTO Reaction (postId, userId, type) VALUES
(2, 2, 'like'),
(4, 4, 'like'),
(1, 1, 'like'),
(3, 3, 'like');
-- Insert Comment
INSERT INTO Comment (postId, userId, comment) VALUES
(2, 4, 'notJust Developers are changing the world one line of code at a time'),
(2, 1, 'nice hoodie!');
-- Insert Experience
INSERT INTO Experience (id, title, companyName, companyImage, userId) VALUES
(1, 'Founder', 'notJust.dev', 'https://media.licdn.com/dms/image/C4E0BAQGmtxs_h3L1fg/company-logo_100_100/0/1646644437762?e=1697673600&v=beta&t=krLyDvQnoEVEBn1MlGrFEM8cpt5Y24XvXJV7RjXyGsc', 2),
(2, 'Co-Founder & CTO', 'Fitenium', 'https://media.licdn.com/dms/image/C4D0BAQHg4ra02yFrcw/company-logo_200_200/0/1573813337379?e=1697673600&v=beta&t=3wUnjLtfkYpIMJg4ZP2qq7Vj0rvU7aUOVrkffLabTuQ', 2);

Query a table

Now that we have our tables created and we added some data, let’s query the tables and make sure everything is ready for the next step. The following command should select all the rows and columns from the Post table and display them in the table below.

BASH
SELECT * From Post;

GraphQL API with StepZen

At this moment, we have a React Native application and a Database.

What we are still missing, is a backend API between the client and the database.

Let’s build a GraphQL API, because it will make querying the correct data easier and more optimal.

▶️ Check out this video to learn more about the benefits of GraphQL APIs

The easiest way to build a scalable GraphQL API is StepZen. With one simple CLI command, we will have a GraphQL API up and running. It’s really that simple.

Setup the StepZen CLI

  1. Let’s install stepzen CLI using npm install -g stepzen.
  2. Sign up for a free StepZen account
  3. Login inside your terminal using stepzen login and provide the details from StepZen Dashboard

Create a GraphQL API based on a PostgreSQL database

Docs: https://stepzen.com/docs/quick-start/with-database-postgresql

We are ready to create our GraphQL API.

Create a new folder inside our React Native project, and navigate there:

BASH
mkdir stepzen && cd stepzen

Now all we have to do, is run a command and provide the configuration of the DB from Neon

BASH
stepzen import postgresql

After some ✨Magic✨ from StepZen, we have a GraphQL schema automatically generated based on our tables from the database. Open the stepzen/postgresql/index.graphql file and explore the schema.

Deploy the API

Deploying and running the API is as easy as it was creating it. Just run stepzen start and StepZen will deploy the API endpoint to their cloud and you will receive a link for the GraphQL explorer.

Open the explorer and run your first query.

Untitled.png

Query a GraphQL API in React Native with Apollo Client

Docs: https://www.apollographql.com/docs/react/get-started

Apollo Client is a great library for fetching data from a GraphQL API. It also comes with a powerful caching mechanism that helps improve the performance of the application.

Let’s start by installing it inside our React Native project

BASH
npm install @apollo/client graphql

Now, let’s create a new file src/apollo/Client.tsx, and initialize Apollo by connecting it to our GraphQL endpoint.

TYPESCRIPT
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://<ACCOUNT_NAME>].stepzen.net/api/<API_NAME>/__graphql',
headers: {'Authorization':'apikey <API_KEY>'},
cache: new InMemoryCache(),
});
export default client;

Having this client, we need to make it available on all our screens. For that, let’s open src/app/_layout.tsx and wrap our RootLayoutNav inside ApolloProvider

TYPESCRIPT
import client from '@/apollo/Client';
import { ApolloProvider } from '@apollo/client';
...
function RootLayoutNav() {
const colorScheme = useColorScheme();
return (
<ApolloProvider client={client}>
...
</ApolloProvider>
);
}

Query posts on the home feed

We are ready to query the first data from our API inside our Home Feed. Open src/app/(tabs)/index.tsx, define the query and use the useQuery hook to fetch data.

TYPESCRIPT
import { gql, useQuery } from '@apollo/client';
const postList = gql`
query postList {
postList {
id
image
content
profile {
image
id
name
position
}
}
}
`;
export default function HomeFeedScreen() {
const { loading, error, data } = useQuery(postList);
if (loading) return <ActivityIndicator />;
if (error) {
console.log(error);
return <Text>Something went wrong...</Text>;
}
console.log(data.postList);
...
}

Run a mutation

While Queries are for fetching data, Mutations are the GraphQL operation intended to create or update data.

Let’s execute a mutation to create a new post inside our New post page (src/app/(tabs)/new-post.tsx).

TYPESCRIPT
import { gql, useMutation } from '@apollo/client';
const insertPost = gql`
mutation MyMutation($content: String, $image: String, $userId: ID) {
insertPost(content: $content, image: $image, userid: $userId) {
content
id
image
userid
}
}
`;
export default function NewPostScreen() {
...
const [handleMutation, { loading, error, data }] = useMutation(insertPost);
const onPost = async () => {
console.warn(`Posting: ${content}`);
try {
await handleMutation({ variables: { content, userId: 1 } });
router.push('/(tabs)/');
setContent('');
setImage(null);
} catch (e) {
console.log(e);
}
};

Paginated Queries

In most applications, you might want to render long lists of data. For example, on the home feed, we are rendering all the posts from the Database. Now it works, but when we will have more posts in the database, it will take a long time to load them all.

The strategy in this scenario, is to paginate the data. Query only small chunks of data, and when needed, request more data.

StepZen automatically creates paginated queries for all the List queries.

Let’s use a paginated query on our home feed.

TYPESCRIPT
const postListPaginated = gql`
query postListPaginated($first: Int, $after: Int) {
postPaginatedList(first: $first, after: $after) {
id
image
content
profile {
image
id
name
position
}
}
}
`;

For the useQuery hook, we can also send the variable to specify how many items we want to load first.

TYPESCRIPT
const { loading, error, data, fetchMore } = useQuery(postPaginatedList, {
variables: { first: 2 },
});

The useQuery hook, also gives us a fetchMore function. We can use it to fetch the next page.

Let’s use the callback function onEndReached from the flatlist, to fetch more posts.

TYPESCRIPT
<FlatList
...
onEndReached={() =>
fetchMore({ variables: { after: data.postPaginatedList.length } })
}
/>

This is the first part. The pagination will not work properly yet, because Apollo doesn’t know how to merge 2 pages together. For that, we have to define a typePolicy inside our apollo/Client.tsx

TYPESCRIPT
const typePolicies: TypePolicies = {
Query: {
fields: {
postPaginatedList: {
keyArgs: false,
merge(existing = [], incoming) {
return [...existing, ...incoming];
},
},
},
},
};
//Provide it to InMemoryCache
new InMemoryCache({ typePolicies })

Authentication

At this moment, anyone can access our application.

Let’s add an authentication flow so that users can create accounts and sign in.

We will use clerk.com for this.

Let’s follow this guide to add the Sign In and Sign Up screens: https://clerk.com/docs/quickstarts/get-started-with-expo

After the user creates a new account, we have to also add an onboarding step where he can set up his profile. We have to save a new row inside the Profile table.

Conclusions

I hope you enjoyed this tutorial so far.

I encourage you to not stop here. Try to add additional features that might interest you. That’s the best way to learn.


Check other episodes from the  Full-stack LinkedIn Clone  series