[ PART 1 ] Create a Twitter clone with GraphQL, Knex, Typescript and React

[ PART 1 ] Create a Twitter clone with GraphQL, Knex, Typescript and React

Hi everyone! I decided to take up the Twitter challenge of the website devchallenges.io. Having so far made only REST API, I wanted to try GraphQL and it's always more fun to have a nice project and design for that ;).

Tweeter challenge

Before launching myself in this challenge which can be rather complex and especially long ;), I still documented myself before and made a project to try to answer the questions I had about GraphQL (Dataloaders, error management ...).

Disclaimer: This is not a guide/tutorial. It's only a way to document my learning and why not get some feedback ;)

Tech Stack

I decided to go on Node.js with ApolloServer + TypeGraphQL + Typescript + Knex with Postgresql for the backend and the frontend will be with React + ApolloClient for the queries. On my last projects Trello clone and Shoppingify I used to do TDD but this time I'll do some tests but it will probably be much lighter.

Here is the Github repository of the project for those who want to follow my progress ;). Github Repository

Enough talking, let's start coding :D.

yarn add apollo-server graphql type-graphql class-validator knex dotenv pg reflect-metadata
yarn add -D typescript ts-node @types/node nodemon jest

tsconfig.json

{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",                    
    "lib": ["es2018", "esnext.asynciterable"],
    "outDir": "dist",                       
    "rootDir": "src",                       
    "strict": true,                           
    "esModuleInterop": true,                  
    "experimentalDecorators": true,        
    "emitDecoratorMetadata": true,     
    "skipLibCheck": true,                    
    "forceConsistentCasingInFileNames": true
  }
}

package.json

"scripts": {
    "dev": "nodemon src/index.ts --exec ts-node",
    "build": "shx rm -rf dist/ && tsc -p .",
    "start": "node dist/src/index.js"
  },

Creation of the GraphQL server

Once all this is set up, I can finally start my server.

src/index.ts

import 'reflect-metadata'
import { ApolloServer } from 'apollo-server'
import { buildSchema } from 'type-graphql'
import AuthResolver from './resolvers/AuthResolver'

export const createServer = async () => {
  const server = new ApolloServer({
    schema: await buildSchema({
      resolvers: [AuthResolver],
    }),
    context: ({ req, res }) => {
      return {
        req,
        res,
      }
    },
  })

  server.listen().then(({ port }) => {
    console.log(`Listening on port ${port}`)
  })

  return server
}

createServer()

Since I'm going to start by creating users, I start by creating the User entity as well as the AuthResolver.

src/entities/User.ts

import { Field, ID, ObjectType } from 'type-graphql'

@ObjectType()
class User {
  @Field((type) => ID)
  id: number

  @Field()
  username: string

  @Field()
  email: string

  password: string

  @Field()
  created_at: Date

  @Field()
  updated_at: Date
}

export default User

As you can see, my User class does not expose the password field.

src/resolvers/AuthResolver.ts

import { Ctx, Query, Resolver } from 'type-graphql'
import { MyContext } from '../types/types'

@Resolver()
class AuthResolver {
  @Query(() => String)
  async me(@Ctx() ctx: MyContext) {
    return 'Hello'
  }
}

export default AuthResolver

If I test the request, I get my "Hello". So far so good ;).

GraphQL me query

Setting up the database with Knex

To finish this first part, I will set up Knex with a Postgresql database.

knex init -x ts

knexfile.ts


module.exports = {
  development: {
    client: 'pg',
    connection: {
      database: 'challenge_twitter', 
      user: 'postgres',
      password: 'root',
    },
    pool: {
      min: 2,
      max: 10,
    },
    migrations: {
      directory: './src/db/migrations',
    },
    seeds: {
      directory: './src/db/seeds',
    },
  },
  test: {
    client: 'pg',
    connection: {
      database: 'challenge_twitter_test',
      user: 'postgres',
      password: 'root',
    },
    pool: {
      min: 2,
      max: 10,
    },
    migrations: {
      directory: './src/db/migrations',
    },
    seeds: {
      directory: './src/db/seeds',
    },
  },
}

I've created 2 databases for the moment, one for development and the other for testing.

All that's left to do is to create our users table ;).

knex migrate:make create_users_table -x ts

src/db/migrations/create_users_table.ts

import * as Knex from 'knex'

export async function up(knex: Knex): Promise<void> {
  return knex.schema.createTable('users', (t) => {
    t.increments('id')
    t.string('username').notNullable().unique()
    t.string('email').notNullable().unique()
    t.string('password').notNullable()
    t.timestamps(false, true)
  })
}

export async function down(knex: Knex): Promise<void> {
  return knex.raw('DROP TABLE users CASCADE')
}

I start the migration

knex migrate:latest

And all I have to do is create a connection to this database. By the way, I will also install the knex-tiny-logger library to have a simplified visualization of SQL queries ;).

yarn add -D knex-tiny-logger

src/db/connection.ts

import Knex from 'knex'
import KnexTinyLogger from 'knex-tiny-logger'

const config = require('../../knexfile')[process.env.NODE_ENV || 'development']

export default KnexTinyLogger(Knex(config))

All that's left to do is to import this into the index.ts file...

import db from './db/connection'

...and add it to my context to be able to access it from the resolvers.

const server = new ApolloServer({
    schema: await buildSchema({
      resolvers: [AuthResolver],
    }),
    context: ({ req, res }) => {
      return {
        req,
        res,
        db, //Here it is
      }
    },
  })

Here it is for this first part ;). Don't hesitate to tell me if you are interested in me continuing, correcting me when I make mistakes, etc... :D

In the second part, I will set up the test environment.

Have a nice day ;)

You learned 2-3 things and want to buy me a coffee ;)? https://www.buymeacoffee.com/ipscoding