/prisma-graphql-learn

Projet to learn Prisma apollo Graphql

Primary LanguageJavaScript

. . . . . .

Project Name : Api GraphQl, Prisma, Apollo

This project's main goal was to learn new techno.

Table of contents

Description

This project goal was to create an API using graphql. To communicate with the DB i use prisma, i also use apollo-server as graphql server. So you have different model:

  • user
  • risk (with relation to user)
  • defenseProfile (with relation to user)
  • link (with relation to user)

I also implement authentification using JWT

You can create an user,login, create link, a risk, and a defense profil

General info

The Api stack: node.js, express, apollo-server, prisma, graphQl... The Front-end stack: react, axios, bootstap...

Clone the repos, then run npm install

To run the api: npm run start

To run the front: cd front npm run start

Have a look to your databes using prisma studio: npx prisma studio

API Stack

  • node
  • express
  • prisma
  • appollo-server
  • jsonwebtoken
  • graphQl

Prisma

Next-generation Node.js and TypeScript ORM (Object Relational Mapping).

Prisma helps app developers build faster and make fewer errors with an open source database toolkit for PostgreSQL, MySQL, SQL Server, SQLite and MongoDB

Doc

What is Prisma

To be short prisma is use to send queries to our database

  • Prisma Client: Auto-generated and type-safe query builder for Node.js & TypeScript
  • Prisma Migrate: Migration system
  • Prisma Studio: GUI to view and edit data in your database

Data model you can read

Central to Prisma is the schema - a declarative way to define your app's data models and their relations that's human-readable.

Capture d’écran 2022-04-10 à 10 54 03

Model Prisma

model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
}

Model GraphQl

type User {
  id: ID!
  name: String!
  email: String!
}

Apollo server

What is apollo server

Apollo Server is an open-source, GraphQL server that's compatible with any GraphQL client, It's the best way to build a production-ready, self-documenting GraphQL API that can use data from any source.

Doc

What for

  • A stand-alone GraphQL server, including in a serverless environment
  • An add-on to your application's existing Node.js middleware (such as Express or Fastify)
  • A gateway for a federated graph

What he provide

  • Straightforward setup, so your client developers can start fetching data quickly
  • Incremental adoption, allowing you to add features as they're needed
  • Universal compatibility with any data source, any build tool, and any GraphQL client
  • Production readiness, enabling you to ship features faster

How Prisma and Apollo fit together

Apollo provides a great ecosystem for building applications with GraphQL. When building GraphQL APIs with Apollo Server against a database, you need to send database queries inside your GraphQL resolvers – that's where Prisma comes in.

Prisma is an ORM that is used inside the GraphQL resolvers of your Apollo Server to query your database. It works perfectly with all your favorite tools and libraries from the GraphQL ecosystem. Learn more about Prisma with GraphQL.

image

Capture d’écran 2022-04-17 à 12 05 27

More Info: Prisma-apollo

Commands to know

Database

Migration

Example: npx prisma migrate dev --name "add-user-model"

Then: npx prisma generate

Prisma studio

very useful to visualize your DB

npx prisma studio

start the server

npm run start

Querries

Get links with data

query {
  links {
    id
    url
    description
    postedBy{
      id
      name
      email
    }
  }
}

Mutations

signup

mutation {
  signup(name: "Alice", email: "alice@prisma.io", password: "graphql") {
    token
    user {
      id
    }
  }
}

Answer with token

Capture d’écran 2022-04-10 à 11 19 41

Post a new link (Request using header token)

Capture d’écran 2022-04-09 à 18 08 07

Authentfication token

Here come the jwt identification

For more detail: Link

In utils.js file

const jwt = require('jsonwebtoken');
const APP_SECRET = 'GraphQL-is-aw3some';

function getTokenPayload(token) {
  return jwt.verify(token, APP_SECRET);
}
// he getUserId function is a helper function that you’ll call in resolvers which 
// require authentication (such as post). It first retrieves the Authorization header 
// (which contains the User’s JWT) from the context. It then verifies the JWT 
// and retrieves the User’s ID from it. Notice that if that process is not successful 
// for any reason, the function will throw an exception. You can therefore use it to 
// “protect” the resolvers which require authentication.
function getUserId(req, authToken) {
  if (req) {
    const authHeader = req.headers.authorization;
    if (authHeader) {
      const token = authHeader.replace('Bearer ', '');
      if (!token) {
        throw new Error('No token found');
      }
      const { userId } = getTokenPayload(token);
      return userId;
    }
  } else if (authToken) {
    const { userId } = getTokenPayload(authToken);
    return userId;
  }

  throw new Error('Not authenticated');
}

module.exports = {
  APP_SECRET,
  getUserId
};

Then in index.js file

const server = new ApolloServer({
  schema: schemaWithPermissions,
  //This will allow your resolvers to read the Authorization header and validate 
  //if the user who submitted the request is eligible to perform the requested operation.
  context: ({ req }) => {
    return {
      ...req,
      prisma,
      // getting the userId from the token
      userId:
        req && req.headers.authorization
          ? getUserId(req)
          : null
    };
  }
})

Delete cascade

All defense profile are owned, postedBy an user, so when we delete an user, we want to delete the related defenseProfiles.

To realize this actions i used:

onDelete and also onUpdate and set them to Cascade

model DefenseProfile {
  id          Int      @id @default(autoincrement())
  name        String
  level       String
  postedBy    User?    @relation(fields: [postedById], references: [id], onDelete: Cascade, onUpdate: Cascade)
  postedById  Int?
}

API Middleware

I project we have a model user, the user can have different role ("ADMIN", "STAFF",, "VIEWER").

So i wanted to restrict the acces to certain request (permissions)

To handle the permission who can acces this query or this mutation, i used graphql-shield

Capture d’écran 2022-04-17 à 12 14 53

First modify the server

const { ApolloServer, makeExecutableSchema } = require('apollo-server');
const { applyMiddleware } = require('graphql-middleware');
const {permissions} = require('./permissions');

// read the schema.graphql file
const typeDefs = fs.readFileSync(path.join(__dirname, 'schema.graphql'), 'utf-8')

// schema 
const schema = makeExecutableSchema({ typeDefs, resolvers })

// add permissions
const schemaWithPermissions = applyMiddleware(schema, permissions)

const server = new ApolloServer({
  schema: schemaWithPermissions,
  context: ({ req }) => {
    return {
      ...req,
      prisma,
      userId:
        req && req.headers.authorization
          ? getUserId(req)
          : null
    };
  }
})

Permissions

Create a Permissions folder then a index.js file inside

const {  shield, rule, and, or  } = require('graphql-shield');
const { isAdmin, isOwner } = require('./rules');
// and - allows access only if all sub rules used return true,
// or - resolves to true if at least one rule passes,

// list the permissions for all queries and mutations
const permissions = shield({
    Query: {
        // the quey getDefenseProfiles allow only user with admin role
        getDefenseProfiles: isAdmin,
    },
     Mutation: {
        // only the user who create the object can delete it
        deleteDefenseProfile:  isOwner,
        deleteRisk: isOwner,
    },,
})

module.exports = {
    permissions
};

Rules

Now inside Permissions folder create a new file rule.js

const { rule } = require('graphql-shield');

const isAdmin = rule()(async (parent, args, context, info) => {
    const userData = context.prisma.user.findUnique({ where: { id: context.userId } })
    let userIsAdmin
    await userData.then(user => {
        user.role === "ADMIN" ? userIsAdmin = true : userIsAdmin = false
    })
    return userIsAdmin
})

const isOwner = rule()(async (parent, args, context, info) => {
    // delete risk
    if (context.body.operationName === "deleteRisk") {
        const riskDataQ = context.prisma.risk.findUnique({ where: { id: +args.id } })
        const risk = await riskDataQ
        if (risk.postedById === context.userId) {
            return true
        } else {
            return false
        }
    // delete defense profile    
    } else if(context.body.operationName === "deleteDefenseProfile") {
        const defenseProfileDataQ = context.prisma.defenseProfile.findUnique({ where: { id: +args.id } })
        const defenseProfile = await defenseProfileDataQ
        if (defenseProfile.postedById === context.userId) {
            return true
        } else {
            return false
        }
    }
    return false
})

module.exports = {
    isAdmin,
    isOwner
};

😎 Perfect we can now manage the acces of the api for different user role !! 😎

Front End

The front used react, axios, grapql, react-toastify, react-bootstrap... With the frond end you have a login interface, then you can visualize, all risk, all defense profil.

You can also create a new risk, and a defense profil.

cd front

npm run start

Technologies:

  • axios
  • @appollo/client
  • grapql
  • react-router
  • react-toastify
  • bootstrap, react-bootstrap

Screenshots

Playground graphql => http://localhost:4000/:

countryName

Prisma studio to visualize the DB => http://localhost:5555

Capture d’écran 2022-04-09 à 17 38 43

Front-end:

front

ezgif com-gif-maker (3)

What I learn, pratice:

  • database
  • sever
  • graphQL
  • prisma
  • appollo-server
  • jwt
  • permissions
  • middleware
  • node
  • bootstrap
  • toastify
  • etc...

Status

Project is: Finish

Contact

inspired by: https://www.howtographql.com/graphql-js/2-a-simple-query/