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