Apollo Server with TypeScript for Auth

Building Project

Let’s go by steps:

Initial Setup

Initialize Project:

npm init -y

Install the following libraries:

npm i apollo-server-express reflect-metadata pg express dot-env graphql typeorm cors argon2 type-graphql express-session

Lets go over what each of these do:

Create a folder called src/index.ts

npm i typescript @types/express-session ts-node-dev @types/cors @types/express @types/node reflect-metadata -D
npx tsc --init

Which creates a tsconfig.json. Search for rootDir: "./src" and ourDir: "./dist".

Now configure TypeScript by enabling inside your tsconfig.json the experimentalDecorators and emitDecoratorMetadata as true for both cases.

Now do:

npx tsc

Which creates the dist folder.

Inside package.json:

"scripts": {
    "dev": "ts-node-dev src/index.ts",
    "watch": "tsc -w",
    "build": "tsc -p .",
    "start": "node dist/index.js"
}

Basic Configuration

Let’s setup our DB first, create a file src/db.ts:

import { DataSource } from "typeorm";

export const AppDataSource = new DataSource({
  type: "postgres",
  host: "localhost",
  port: 3306,
  username: "test",
  password: "test",
  database: "usersdb",
  synchronize: false,
  ssl: false,
  logging: true,
  entities: [],
  subscribers: [],
  migrations: [],
});

GRAPHQL Setup

Building the basic

For starters lets create three folders inside src called, config, entity and resolvers.

Now create src/app.ts:

import express from "express";
import { ApolloServer } from "apollo-server-express";

const app = express();

const server = new ApolloServer({
  schema,
});

server.applyMiddleware({ app, path: "/graphql" });

Create resolvers/ping.ts which we will use to test our API code mostly:

import { Query, Resolver } from "type-graphql";

@Resolver()
export class PingResolver {
  @Query(() => String)
  ping() {
    return "Pong!";
  }
}

Import this inside app.ts:

import express from "express"
import { ApolloServer } from "apollo-server-express"
import { PingResolver } from "./resolvers/ping"
import { BuildSchema } from "type-graphql"

const startServer = async () => {
    const app = express()
    const server = new ApolloServer({
        schema: await BuildSchema({
            resolvers: [PingResolver]
        }),
        context: ({ req, res } => ())
    })
}


server.applyMiddleware({app, path: "/graphql"})

Now go inside index.ts:

import "reflect-metadata";
import { startServer } from "./app";

async function main() {
  const app = awaitstartServer();
  app.listen(3000);
  console.log("Server on port", 3000);
}

main();

Using TypeORM

Create config/typeorm.ts:

import { createConnection } from "typeorm";

export async function connect() {
  await createConnection({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "",
    database: "", // we will create this bellow
  });
}

Setting MySQL

Connect to MySQL (mysql workbench works but I will use XAMPP and activating APACHE and mySQL options). Inside myPHPAdmin create a new table and call it whatever you want, I will call it usersdb

Inside config/typeorm.ts add the database name to the connection:

import { createConnection } from "typeorm";
import path from "path";

export async function connect() {
  await createConnection({
    type: "mysql",
    host: "localhost",
    port: 3306,
    username: "root",
    password: "",
    database: "usersdb",
    entities: [path.join(__dirname, "../entity/**/**.ts")],
    synchronyze: true,
  });
}

Now go inside index.ts and import our connection:

import "reflect-metadata";
import { connect } from "./config/typeorm";
import { startServer } from "./app";

async function main() {
  connect();
  const app = awaitstartServer();
  app.listen(3000);
  console.log("Server on port", 3000);
}

main();

Now run npm run dev and it will work!

Then enter and USE usersdb; and it will show a message saying “Database changed”

Create a file src/db.ts:

import { createConnection } from "typeorm";

export const connectDB = async () => {
  await createConnection({
    type: "mysql",
    username: "root", // USE YOURS!
    password: "password",
    port: 3306,
    host: "localhost",
    database: "usersdb",
    entities: [], // we fill it later
    synchronize: false,
    ssl: false,
  });
};

Create a file src/app.ts and add everything in index.ts:

import express from "express";
import { graphqlHTTP } from "express-graphql";
import { schema } from "./schema";

const app = express();

app.use(
  "/graphql",
  graphqlHTTP({
    graphiql: true,
    schema,
  })
);

export default app;

Now inside index.ts:

import app from "./app";
import { connectDB } from "./db";

async function main() {
  try {
    await connectDB();
    app.listen(3000);
    console.log("Server is ", 3000);
  } catch (error) {
    console.error(error);
  }
}

main();

Now we need to configure the DB, go inside the DB terminal:

ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY 'password'

Hit enter and flush privileges and npm run dev.

Setting Entity

Create src/Entities/Users.ts:

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
  BaseEntity,
} from "typeorm";

@Entity()
export class Users extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;
  @Column()
  name: string;
  @Column()
  username: string;
  @Column()
  password: string;
  @Column({ unique: true })
  email: string;
  @Column({
    default: true,
  })
  active: boolean;
  @CreateDateColumn()
  createdAt: Date;
  @UpdateDateColumn()
  updatedAt: Date;
}

Inside your tsconfig.json:

"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,

Go into db.ts:

import { createConnection } from "typeorm";
import { Users } from "./Entities/Users";

export const connectDB = async () => {
  await createConnection({
    type: "mysql",
    username: "root", // USE YOURS!
    password: "password",
    port: 3306,
    host: "localhost",
    database: "usersdb",
    entities: [Users],
    synchronize: false, // change to true
    ssl: false,
  });
};

Go to MySQL terminal show databases; and use usersdb; and show tables;, this shows you haven’t created tables.

Now make synchronize as true and save (which will reset your terminal), then hit show tables; and now it will show!

Next do describe users; and you can see all the data inside.

Create User

Inside src/schema/typeDefs/User.ts:

import { GraphQLObjectType, GraphQLID } from "graphql"


export new GraphQLObjectType({
    name: 'User',
    fields: {
        id: {  type: GraphQLID},
        name: {  type: GraphQLString},
        username: {  type: GraphQLString},
        password: {  type: GraphQLString},
    },
})

Inside src/schema/Mutations/User.ts:

import { GraphQLString } from "graphql"
import { Users } from "../../Entities/Users"
import bcrypt from "bcryptjs"

import { UserType } from "../typeDefs/User"

export const CREATE_USER = {
    type: UserType,
    args: {
        name: {  type: GraphQLString},
        username: {  type: GraphQLString},
        password: {  type: GraphQLString},
    },
    async resolve(_: any, args: any) {
        const { name, username, password } = args

        const encryptPassword = await bcrypt.hash(password, 10)

        const result = await Users.insert({
            name,
            username,
            password: encryptPassword,
        })

        return {
            ...args
            id: result.identifiers[0].id,
            password: encryptPassword
        }
    }
}

Inside src/schema/index.ts:

import { GraphqlSchema GraphQLObjectType } from "graphql"
import { GREETING } from "./Queries/Greeting"
import { CREATE_USER } from "./Mutations/User"

const RootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    fields: {
        greeting: GREETING
    }
})

const Mutation = new GraphQLObjectType({
    name: "Mutation",
    fields: {
        createUser: CREATE_USER,
    },
})

export const schema = new GraphqlSchema({
    query: RootQuery,
    mutation: Mutation
})

Inside localhost:3000/graphql:

mutation {
  createUser(name: "ryan", username: "ryan123", password: "ryan123") {
    id
    name
    username
    password
  }
}

It returns:

{
  "data": {
    "createUser": {
      "id": "0",
      "name": "ryan",
      "username": "ryan123",
      "password": "asdfasdgfafhafgasdfsdfdfad"
    }
  }
}

The password is encrypted.

GET ALL Users

Create src/schema/Queries/User.ts

import { GraphQLList } from "graphql";
import { UserType } from "../typeDefs/User";
import { Users } from "../../Entities/Users";

export const GET_ALL_USERS = {
  type: new GraphQLList(UserType), // UserType[]
  async resolver() {
    const result = await Users.find();
    return result;
  },
};

Go to src/Schema/index.ts:

import { GraphqlSchema GraphQLObjectType } from "graphql"
import { GREETING } from "./Queries/Greeting"
import { GET_ALL_USERS } from "./Queries/User.ts"
import { CREATE_USER } from "./Mutations/User"

const RootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    fields: {
        greeting: GREETING,
        getAllUsers: GET_ALL_USERS,
    }
})

const Mutation = new GraphQLObjectType({
    name: "Mutation",
    fields: {
        createUser: CREATE_USER,
    },
})

export const schema = new GraphqlSchema({
    query: RootQuery,
    mutation: Mutation
})

Inside localhost:3000/graphql:

query {
  getAllUsers {
    id
    name
    username
    password
  }
}

GET User By Id

Create src/schema/Queries/User.ts

import { GraphQLList } from "graphql";
import { UserType } from "../typeDefs/User";
import { Users } from "../../Entities/Users";

export const GET_ALL_USERS = {
  type: new GraphQLList(UserType), // UserType[]
  async resolver() {
    const result = await Users.find();
    return result;
  },
};

export const GET_USER = {
  type: UserType,
  args: {
    id: { type: GraphQLID },
  },
  async resolver(_: any, args: any) {
    const result = await Users.findOne(args.id);
    return result;
  },
};

Go to src/Schema/index.ts:

import { GraphqlSchema GraphQLObjectType } from "graphql"
import { GREETING } from "./Queries/Greeting"
import { GET_ALL_USERS, GET_USER } from "./Queries/User.ts"
import { CREATE_USER } from "./Mutations/User"

const RootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    fields: {
        greeting: GREETING,
        getAllUsers: GET_ALL_USERS,
        getUser: GET_USER,
    }
})

const Mutation = new GraphQLObjectType({
    name: "Mutation",
    fields: {
        createUser: CREATE_USER,
    },
})

export const schema = new GraphqlSchema({
    query: RootQuery,
    mutation: Mutation
})

Inside localhost:3000/graphql:

query {
  getUser(id: 1) {
    id
    name
    username
    password
  }
}

DELETE User

Inside src/schema/Mutations/User.ts:

import { GraphQLString, GraphQLBoolean } from "graphql"
import { Users } from "../../Entities/Users"
import bcrypt from "bcryptjs"

import { UserType } from "../typeDefs/User"

export const CREATE_USER = {
    type: UserType,
    args: {
        name: {  type: GraphQLString},
        username: {  type: GraphQLString},
        password: {  type: GraphQLString},
    },
    async resolve(_: any, args: any) {
        const { name, username, password } = args

        const encryptPassword = await bcrypt.hash(password, 10)

        const result = await Users.insert({
            name,
            username,
            password: encryptPassword,
        })

        return {
            ...args
            id: result.identifiers[0].id,
            password: encryptPassword
        }
    }
}

export const DELETE_USER = {
    type: GraphQLBoolean,
    args: {
        id: { type: GraphQLID },
    },
    async resolver(_:any, { id }: any) {
        const result = await Users.delete(id)
        if (result.affected === 1) return true
        return false
    }
}

Go to src/Schema/index.ts:

import { GraphqlSchema GraphQLObjectType } from "graphql"
import { GREETING } from "./Queries/Greeting"
import { GET_ALL_USERS, GET_USER } from "./Queries/User.ts"
import { CREATE_USER, DELETE_USER } from "./Mutations/User"

const RootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    fields: {
        greeting: GREETING,
        getAllUsers: GET_ALL_USERS,
        getUser: GET_USER,
    }
})

const Mutation = new GraphQLObjectType({
    name: "Mutation",
    fields: {
        createUser: CREATE_USER,
        deleteUser: DELETE_USER,
    },
})

export const schema = new GraphqlSchema({
    query: RootQuery,
    mutation: Mutation
})

Inside localhost:3000/graphql:

mutation {
  deleteUser(id: "7")
}

UPDATE User

Create src/schema/typeDefs/Message.ts:

import { GraphQLString, GraphQLBoolean, GraphqlObjectType } from "graphql";

export const MessageType = new GraphqlObjectType({
  name: "Message",
  fields: {
    success: { type: GraphQLBoolean },
    message: { type: GraphQLString },
  },
});

Inside src/schema/Mutations/User.ts:

import {
    GraphQLString,
    GraphQLBoolean,
    GraphQLInputObjectType
} from "graphql"
import { Users } from "../../Entities/Users"
import bcrypt from "bcryptjs"

import { UserType } from "../typeDefs/User"
import { MessageType } from "../typeDefs/Message"

export const CREATE_USER = {
    type: UserType,
    args: {
        name: {  type: GraphQLString},
        username: {  type: GraphQLString},
        password: {  type: GraphQLString},
    },
    async resolve(_: any, args: any) {
        const { name, username, password } = args

        const encryptPassword = await bcrypt.hash(password, 10)

        const result = await Users.insert({
            name,
            username,
            password: encryptPassword,
        })

        return {
            ...args
            id: result.identifiers[0].id,
            password: encryptPassword
        }
    }
}

export const DELETE_USER = {
    type: GraphQLBoolean,
    args: {
        id: { type: GraphQLID },
    },
    async resolver(_:any, { id }: any) {
        const result = await Users.delete(id)
        if (result.affected === 1) return true
        return false
    }
}


export const UPDATE_USER = {
    type: MessageType,
    args: {
        id: { type: GraphQLID },
        input: {
            type: new GraphQLInputObjectType({
                name: "UserInput",
                fields: {
                    name: {  type: GraphQLString},
                    username: {  type: GraphQLString},
                    oldPassword: {  type: GraphQLString},
                    newPassword: {  type: GraphQLString},
                }
            })
        }

    },
    async resolve(_: any, { id, input }: any) {
        const userFound = await Users.findOne(id)

        if(!userFound) return {
            success: false,
            message: "User not found"
        }

        const isMatch = await bcrypt.compare(input.oldPassword, userFound.password)

        if (!isMatch) return {
            success: false,
            message: "Old password is incorrect"
        }

        const hashedPass = await bcrypt.hash(input.newPassword, 10)

        const result = await Users.update({id}, {
            username: input.username,
            name: input.name,
            password: hashedPass
        })

        if (result.affected === 0) return {
            success: false,
            message: "No rows were affected"
        }

        return {
            success: true,
            message: "User updated successfully"
        }
    }
}

Go to src/Schema/index.ts:

import { GraphqlSchema GraphQLObjectType } from "graphql"
import { GREETING } from "./Queries/Greeting"
import { GET_ALL_USERS, GET_USER } from "./Queries/User.ts"
import {
    CREATE_USER,
    DELETE_USER,
    UPDATE_USER,
} from "./Mutations/User"

const RootQuery = new GraphQLObjectType({
    name: 'RootQuery',
    fields: {
        greeting: GREETING,
        getAllUsers: GET_ALL_USERS,
        getUser: GET_USER,
    }
})

const Mutation = new GraphQLObjectType({
    name: "Mutation",
    fields: {
        createUser: CREATE_USER,
        deleteUser: DELETE_USER,
        updateUser: UPDATE_USER,
    },
})

export const schema = new GraphqlSchema({
    query: RootQuery,
    mutation: Mutation
})

Inside localhost:3000/graphql:

mutation {
  updateUser(
    id: "7"
    input: {
      name: "Jose"
      userName: "El Jose"
      oldPassword: "password"
      newPassword: "eljosepassword"
    }
  ) {
    success
    message
  }
}

ENV Variables

Do npm run build, after that inside package.json:

"scripts": {
    ...,
    "start": "node dist/index.js"
}

Create .gitignore:

dist
node_modules
.env

Now git init and .env:

DB_PASSWORD=password // use yours!
DB_USERNAME=root
DB_HOST=localhost
DB_PORT=3306
DB_NAME=usersdb

Now create src/config.ts:

import { config } from "dotenv";

config();

export const PORT = process.env.PORT || 3000;

export const DB_NAME = process.env.DB_NAME;
export const DB_PASSWORD = process.env.DB_PASSWORD;
export const DB_HOST = process.env.DB_HOST;
export const DB_PORT = process.env.DB_PORT;
export const DB_USERNAME = process.env.DB_USERNAME;

This will load env variables in your computer.

Inside your db.ts:

import { createConnection } from "typeorm"
import { Users } from "./Entities/Users"

import {
    DB_NAME,
    DB_PASSWORD,
    DB_HOST,
    DB_PORT,
    DB_USERNAME
} "./config"

export const connectDB = async () => {
    await createConnection({
        type: 'mysql',
        username: DB_USERNAME,
        password: DB_PASSWORD,
        port: Number(DB_PORT),
        host: DB_HOST,
        database: DB_NAME,
        entities: [Users],
        synchronize: false,
        ssl: false,
    })
}

Now go inside your index.ts:

import app from "./app";
import { connectDB } from "./db";

import { PORT } from "./config";

async function main() {
  try {
    await connectDB();
    app.listen(PORT);
    console.log("Server is ", PORT);
  } catch (error) {
    console.error(error);
  }
}

main();

Now git add ., git commit -am "Initial commit", git push origin main.

DEPLOY

Use DIGITAL OCEAN or any other.

Conclusion

We managed to learn TypeOrm! Its quite easy to use, same as Prisma was. For this case it was a simple CRUD.

We created a simple GRAPHQL app, it has more steps than the REST API, but it brings the benefit of only sending to the frontend what is required which is a big improvement over REST.

See you on the next post.

Sincerely,

Eng. Adrian Beria