[ PART 1 ] Creating a Twitter clone with GraphQL, Knex, Typescript and React
Tags: career, webdev, showdev, graphql
Published: 2021-01-05 18:45:10

Hi everyone! I decided to take up the Twitter challenge of the website [devchallenges.io](https://devchallenges.io/challenges/rleoQc34THclWx1cFFKH). 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](https://firebasestorage.googleapis.com/v0/b/devchallenges-1234.appspot.com/o/challengesDesigns%2FTweeterThumbnail.png?alt=media&token=c49105bd-1854-4a6e-ab18-ca424a325332)

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](https://dev.to/ipscodingchallenge/continuing-my-learning-path-i-did-a-trello-clone-55el) and [Shoppingify](https://dev.to/ipscodingchallenge/doing-projects-to-learn-is-great-doing-it-with-someone-is-a-lot-better-3g31) 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](https://github.com/ips-coding-challenge/challenge_twitter)

Enough talking, let's start coding :D.

```sh
yarn add apollo-server graphql type-graphql class-validator knex dotenv pg reflect-metadata
```

```sh
yarn add -D typescript ts-node @types/node nodemon jest
```

*tsconfig.json*

```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*

```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*

```typescript
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*

```typescript
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*

```typescript
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](https://dev-to-uploads.s3.amazonaws.com/i/ybvhlsqim6dosvvj43fs.png)

## Setting up the database with Knex

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

```sh
knex init -x ts
```

*knexfile.ts*

```typescript

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 ;).

```sh
knex migrate:make create_users_table -x ts
```

*src/db/migrations/create_users_table.ts*

```typescript
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

```sh
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 ;).

```sh
yarn add -D knex-tiny-logger
```

*src/db/connection.ts*

```typescript
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...

```typescript
import db from './db/connection'
```
...and add it to my context to be able to access it from the resolvers.

```typescript
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 setup the test environment.

Have a nice day ;) 

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