Node va MYSQL 2019 uchun - JWT uchun Grafql Api yarating

Agar siz bu erda bo'lsangiz, ehtimol siz allaqachon bilasiz. Bilasizmi, Graphql juda ajoyib, rivojlanishni tezlashtiradi va Tesla S modelini chiqqandan beri sodir bo'lgan eng yaxshi narsa.

Mana men foydalanadigan yangi shablon: https://medium.com/@brianschardt/best-graphql-apollo-sql-and-nestjs-template-458f9478b54e

Ammo, men o'qigan darslarning aksariyati grafql dasturini qanday tuzish kerakligini, ammo n + 1 talablarining umumiy muammosini qanday tanishtirishni ko'rsatib beradi. Natijada, ishlash odatda juda yomon.

Haqiqatan ham bu Tesla-dan yaxshiroqmi?

Ushbu maqoladagi mening maqsadim Graphql-ning asoslarini tushuntirish emas, balki kimgadir n + 1 muammosi bo'lmagan Graphql API-ni qanday tez qurish kerakligini ko'rsatishdir.

Agar siz yangi dasturlarning 90% nima uchun grafql api-dan foydalanishi kerakligini bilmoqchi bo'lsangiz, bu erni bosish o'rniga.

Videoga qo'shimcha:

Ushbu shablon ishlab chiqarishda ishlatilishi kerak, chunki u atrof-muhit o'zgaruvchilarini boshqarishning oson usullarini o'z ichiga oladi va kod qo'ldan chiqmasligi uchun tashkillashtirilgan tuzilishga ega. N + 1 muammosini boshqarish uchun biz ushbu muammoni hal qilish uchun feysbukdan chiqarilgan ma'lumotlar yuklanishidan foydalanamiz.

Tasdiqlash: JWT

ORM: O'tkazib yuboring

Ma'lumotlar bazasi: Mysql yoki Postgres

Ishlatilgan boshqa muhim paketlar: ekspress, apollo-server, graphql-sequelize, dataloader-sequelize

Izoh: Ilova uchun typcripts ishlatiladi. Bu javascript-ga juda o'xshash, agar siz hech qachon typcript-ni ishlatmagan bo'lsangiz, men tashvishlanmayman. Ammo, agar etarli talab bo'lsa, men oddiy javascript versiyasini yozaman. Agar xohlasangiz, sharh bering.

Ishni boshlash

Reponi klonlash va tugun modullarini o'rnatish

Repo-ga havolasi bor, uni eng yaxshi tarzda kuzatib borish uchun uni klonlashtirishni maslahat beraman.

git klon git@github.com: brianschardt / node_graphql_apollo_template.git
CD node_graphql_apollo_template
npm o'rnatish
// dasturni ishga tushirish uchun global paketlarni o'rnating
npm i -g tuguncha

.Env bilan boshlaylik

Example.env nomini .env deb o'zgartiring va uni atrofingiz uchun to'g'ri ma'lumotlarga o'zgartiring.

NODE_ENV = ishlab chiqish

PORT = 3001

DB_HOST = localhost
DB_PORT = 3306
DB_NAME = turi
DB_USER = ildiz
DB_PASSWORD = ildiz
DB_DIALECT = mysql

JWT_ENCRYPTION = randomEncryptionKey
JWT_EXPIRATION = 1 yil

Kodni ishga tushiring

Endi agar siz ma'lumotlar bazasi ishlayotgan bo'lsa va .env faylingizni to'g'ri ma'lumot bilan yangilagan bo'lsangiz, biz ilovamizni ishga tushirishimiz kerak. Bu ma'lumotlar bazasida avtomatik ravishda aniqlangan sxemasi bo'lgan jadvallarni yaratadi.

// rivojlanish uchun foydalaning, chunki kod o'zgarishini kuzatadi.
npm yugurish boshlanishi: tomosha qilish
// ishlab chiqarish uchun foydalanish
npm yugurish boshlanishi

Endi brauzeringizni yarating va kiriting: http: // localhost: 3001 / graphql

Endi siz grafik o'yin maydonchasini ko'rishingiz kerak, bu sizga qanday mutatsiyalar va so'rovlar mavjudligi to'g'risidagi hujjatlarni ko'rishga imkon beradi. Shuningdek, bu sizga API-ga qarshi so'rovlar qilish imkonini beradi. Ulardan ikkitasi allaqachon qilingan, ammo ushbu shablon API kuchini sinab ko'rish uchun siz ma'lumotlar bazasini qo'lda ekmoqchi bo'lishingiz mumkin.

Ma'lumotlar bazasi va Grafql sxemasi

Diagrammaning grafik maydonchasida ko'rib turganingizdek, u juda oddiy tuzilishga ega. Faqat ikkita jadval mavjud, ya'ni foydalanuvchi va kompaniya. A'zo bitta kompaniyaga tegishli bo'lishi mumkin va kompaniya ko'plab foydalanuvchilarga ega bo'lishi mumkin, ya'ni bitta assotsiatsiyaga kiradi.

Foydalanuvchi yaratish

Foydalanuvchi yaratish uchun o'yin maydonchasida ishlash uchun gql misol. Bu, shuningdek, kelgusidagi so'rovlar uchun haqiqiyligini tekshirish uchun JWT-ni qaytaradi.

mutatsiya {
  createUser (ma'lumotlar: {first name: "test", elektron pochta: "test@test.com", parol: "1"}) {
    id
    ism
    jwt
  }
}

Haqiqiylikni tekshirish:

Endi sizda JWT bo'lsa, gql o'yin maydonchasi yordamida hamma narsa to'g'ri ishlayotganiga ishonch hosil qilish uchun sinovdan o'tganlikni tekshirishga imkon beradi. Veb-sahifaning chap pastki qismida HTTP rahbarlari aytadigan matn bo'ladi. Unga bosing va buni kiriting:

Eslatma: token bilan almashtiring.

{
  "Avtorizatsiya": "Taqdim etuvchi eyJhbGciOiJ ..."
}

Endi ushbu so'rovni o'yin maydonchasida bajaring:

so'rov {
  getUser {
    id
    ism
  }
}

Agar hamma narsa sizning ismingiz va foydalanuvchi identifikatoringiz bilan ishlasa, sizga qaytarilishi kerak.

Endi agar siz o'zingizning ma'lumotlar bazangizni kompaniya nomi va identifikatori bilan qo'lda ekib qo'ysangiz va foydalanuvchingizga ushbu identifikatorni tayinlasangiz va ushbu so'rovni bajarsangiz. Kompaniyani qaytarish kerak.

so'rov {
  getUser {
    id
    ism
    kompaniya {
      id
      nomi
    }
  }
}

Ushbu API-ni qanday ishlatishni va sinovdan o'tkazishni bilsangiz, endi kodga kirishingiz mumkin!

Kod sho'ng'in

Asosiy fayl - app.ts

Load Depancencies - db modellarini yuklaydi va o'zgaruvchilar.

"express" dan ekspress sifatida import qilish;
'express-jwt' dan jwt sifatida import qilish;
'Apollo-server-express' dan {ApolloServer} ni import qilish;
'./models' dan {sequelize} ni import qilish;
'./config' dan {ENV} ni import qilish;

'./graphql' -dan {ravshanlikni hal qiluvchi, sxema, schemaDirectives} sifatida import qiling;
"dataloader-sequelize" dan {createContext, EXPECTED_OPTIONS_KEY} ni import qilish;
'wait-to-js' -dan import qilish;

const app = express ();

O'rta dastur va Apollo Server-ni sozlang!

Izoh: "yaratish konteksti (ketma-ketlik)" bu n + 1 muammosidan xalos bo'ladi. Bularning barchasi hozir ketma-ketlikda fonda amalga oshiriladi. Sehrli !! Bunda facebook dataloader paketidan foydalaniladi.

const authMiddleware = jwt ({
    sir: ENV.JWT_ENCRYPTION,
    hisobga olish ma'lumotlariSozlanishi: yolg'on,
});
app.use (authMiddleware);
app.use (funktsiya (err, req, res, next) {
    const errorObject = {xato: to'g'ri, xabar: `$ {err.name}:
$ {err.message} `};
    if (err.name === 'RuxsatsizError') {
        qaytish res.status (401) .json (errorObject);
    } else {
        qaytish res.status (400) .json (errorObject);
    }
});
const server = yangi ApolloServer ({
    typeDefs: sxema,
    hal qiluvchi,
    schemaDirectives,
    o'yin maydoni: haqiqiy,
    kontekst: ({req}) => {
        return {
            [EXPECTED_OPTIONS_KEY]: yaratishKontekst (navbatma-navbat),
            foydalanuvchi: req.user,
        }
    }
});
server.applyMiddleware ({app});

So'rovlarni tinglang

app.listen ({port: ENV.PORT}, async () => {
    console.log (`http http: // localhost-da server tayyor: $ {ENV.PORT} $ {server.graphqlPath}`);
    yo'l qo'yib ber
    [err] = kuting (sequelize.sync (
        // {kuch: haqiqiy},
    ));

    agar (xato) {
        console.error ('Xato: ma'lumotlar bazasiga ulanib bo'lmadi');
    } else {
        console.log ('Ma'lumotlar bazasiga ulangan');
    }
});

Konfiguratsiya o'zgaruvchilari - config / env.config.ts

Biz .env o'zgaruvchilarini bizning ilovamizga yuklash uchun dotenv-dan foydalanamiz.

'dotenv' dan dotEnv sifatida import qilish;
dotEnv.config ();

eksport const ENV = {
    PORT: process.env.PORT || '3000',

    DB_HOST: process.env.DB_HOST || '127.0.0.1',
    DB_PORT: process.env.DB_PORT || '3306',
    DB_NAME: process.env.DB_NAME || 'dbName',
    DB_USER: process.env.DB_USER || 'ildiz',
    DB_PASSWORD: process.env.DB_PASSWORD || 'ildiz',
    DB_DIALECT: process.env.DB_DIALECT || 'mysql',

    JWT_ENCRYPTION: process.env.JWT_ENCRYPTION || 'xavfsizKey',
    JWT_EXPIRATION: process.env.JWT_EXPIRATION || '1y',
};

Grafql vaqti !!!

Keling, ushbu echimlarni ko'rib chiqaylik!

grafik / indeks.ts

Bu erda biz paket sxemasi elimidan foydalanamiz. Bu toza va tartibli kodni saqlash uchun diagramma, so'rovlar va mutatsiyalarni alohida qismlarga ajratishga yordam beradi. Ushbu paket avtomatik ravishda 2 ta fayl uchun, ya'ni sxema.graphql va fixver.ts uchun katalogni qidiradi. Keyin ularni ushlab, bir-biriga yopishtiradi. Shuning uchun nom sxemasi elim.

Direktivalar: bizning ko'rsatmalarimiz uchun biz ular uchun katalog yaratamiz va ularni index.ts fayli orqali kiritamiz.

'sxemaglue' dan elim sifatida import qiling;
'./directives' dan {schemaDirectives} ni eksport qilish;
eksport const {sxema, hal qiluvchi} = elim ('src / graphql', {rejimi: 'ts'});

Har bir model uchun moslik uchun kataloglar yaratamiz. Shunday qilib, bizda foydalanuvchi va kompaniya katalogi mavjud.

grafik / foydalanuvchi

Biz hal qiluvchi faylni, hatto sxema elimidan foydalanganda ham juda katta hajmga ega bo'lishini ko'rdik. Shunday qilib, biz uni so'rov, mutatsiya yoki tur xaritasi asosida tuzishga qaror qildik. Shunday qilib, bizda yana 3 ta fayl mavjud.

  • user.query.ts
  • user.mutation.ts
  • user.map.ts

Eslatma: Agar siz gql obunalarini qo'shmoqchi bo'lsangiz, boshqa nomdagi faylni yaratgan bo'lar edingiz: user.subscription.ts va uni hal qiluvchi faylga qo'shing.

grafik / foydalanuvchi / ravshan.ts

Ushbu fayl juda oddiy va ushbu katalogdagi boshqa fayllarni tartibga solish uchun serverlar mavjud.

'./user.query' dan {Query} ni import qilish;
"./user.map" dan {UserMap} ni import qilish;
"./user.mutation" dan {Mation} import qilish;

eksport const hal qiluvchi = {
  So‘rov: so‘rov,
  Foydalanuvchi: UserMap,
  Mutatsiya: Mutatsiya
};

grafik / foydalanuvchi / schema.graphql

Ushbu fayl bizning grafik sxemamizni va hal qiluvchilarni aniqlaydi! Super muhim!

foydalanuvchi {
  id: Int
  elektron pochta: satr
  firstName: String
  lastName: satr
  kompaniya: Kompaniya
  jwt: satr @isAuthUser
}

UserInput {kiritish
    elektron pochta: satr
    parol: String
    firstName: String
    lastName: satr
}

so'rov turi {
   getUser: foydalanuvchi @isAuth
   loginUser (elektron pochta: String!, parol: String!): Foydalanuvchi
}

turi Mutatsiya {
   createUser (ma'lumotlar: UserInput): foydalanuvchi
}

graphql / foydalanuvchi / user.query.ts

Ushbu fayl bizning barcha foydalanuvchi so'rovlarimiz va mutatsiyalarimiz uchun funktsional imkoniyatlarni o'z ichiga oladi. Grafikl-ketma-ketlik sehridan ko'p grafiklarni boshqarish uchun foydalanadi. Agar siz boshqa grafik paketlardan foydalansangiz yoki o'zingizning grafql api-ni yaratishga harakat qilsangiz, ushbu paketni tejash qanchalik muhimligini bilib olasiz. Shunday bo'lsa-da, u sizga hech qachon kerak bo'ladigan barcha moslashni ta'minlaydi! Mana shu paketdagi hujjatlarga havola.

'graphql-sequelize' -dan {Resolutionver} -ni import qiling;
import qilish {User} dan '../../models';
'wait-to-js' -dan import qilish;

eksport const Query = {
    getUser: fixer (Foydalanuvchi, {
        oldin: async (findOptions, {}, {user}) => {
            return findOptions. erda = {id: user.id};
        },
        keyin: (foydalanuvchi) => {
            foydalanuvchini qaytarish;
        }
    }),
    loginUser: fixer (Foydalanuvchi, {
        oldin: async (findOptions, {email}) => {
            findOptions.where = {email};
        },
        keyin: async (foydalanuvchi, {parol}) => {
            yo'l qo'yib ber
            [err, user] = kuting (user.comparePassword (parol));
            agar (xato) {
              console.log (err);
              yangi Error (err) ni tashlash;
            }

            user.login = true; // ko'rsatma, bu foydalanuvchi avtorizatsiya sarlavhasiz autentifikatsiya qilinganligini bilish uchun
            foydalanuvchini qaytarish;
        }
    }),
};

grafik / foydalanuvchi / user.mutation.ts

Ushbu faylda bizning ilovamizning foydalanuvchi qismi uchun barcha mutatsiyalar mavjud.

'graphql-sequelize' -dan {Resolutionver-ni rs} sifatida import qilish;
import qilish {User} dan '../../models';
'wait-to-js' -dan import qilish;

eksport const Mutatsiya = {
    createUser: rs (Foydalanuvchi, {
      oldin: async (findOptions, {data}) => {
        let adr, user;
        [err, user] = kuting (User.create (ma'lumotlar));
        agar (xato) {
          otish adash;
        }
        findOptions.where = {id: user.id};
        return findOptions;
      },
      keyin: (foydalanuvchi) => {
        user.login = haqiqiy;
        foydalanuvchini qaytarish;
      }
    }),
};

graphql / foydalanuvchi / user.map.ts

Odamlar har doim e'tiborsiz qoldiradigan narsa, grafikda kodlash va so'rovni qiyinlashtiradigan va ishlashi sust bo'lgan narsa. Biroq, biz kiritilgan barcha paketlar muammoni hal qiladi. Bir-birlarini turlarga ajratish - bu grafqlga uning kuchi va kuchini beradigan narsa, ammo odamlar bu kuchni zaiflikka aylantiradigan tarzda kodlaydilar. Biroq, biz foydalangan barcha paketlar bu osonlikcha yo'q qilinadi.

'graphql-sequelize' -dan {Resolutionver} -ni import qiling;
import qilish {User} dan '../../models';
'wait-to-js' -dan import qilish;

eksport const UserMap = {
    kompaniya: fixver (User.associations.company),
    jwt: (foydalanuvchi) => user.getJwt (),
};

Ha, bu juda sodda !!!

Izoh: foydalanuvchi sxemasidagi grafik ko'rsatmalar - bu foydalanuvchi maydonidagi JWT maydoni va getUser so'rovi kabi ba'zi maydonlar himoyasini ta'minlaydi.

Modellar - modellar / index.ts

Ushbu sinf turiga o'zgaruvchilar o'rnatish uchun biz ketma-ket yozilgan skriptlardan foydalanamiz. Ushbu faylda biz paketlarni yuklashni boshlaymiz. Keyin biz ketma-ketlikni o'rnatamiz va uni db-ga bog'laymiz. Keyin biz modellarni eksport qilamiz.

'sequelize-typcript' dan {Sequelize} -ni import qilish;
'../config/env.config' dan {ENV} ni import qilish;

export const sequelize = yangi Sequelize ({
        ma'lumotlar bazasi: ENV.DB_NAME,
        lahjasi: ENV.DB_DIALECT,
        foydalanuvchi nomi: ENV.DB_USER,
        parol: ENV.DB_PASSWORD,
        operatorlarAlazlar: yolg'on,
        ro'yxatga olish: noto'g'ri,
        saqlash: ': xotira:',
        model Yo'llari: [__dirname + '/*.model.ts'],
        modelMatch: (fayl nomi, a'zo) => {
           return filename.substring (0, filename.indexOf ('. model')) === member.toLowerCase ();
        },
});
'./user.model' dan {User} -ni eksport qilish;
eksport qilish {Company} 'dan ./company.model';

ModelPaths va modelMatch qo'shimcha modellar bo'lib, ular bizning ketma-ketlarimiz va ularning nomlanish shartlari haqida ketma-ket yozilgan skriptlarni aytib beradi.

Kompaniya Model - modellar / company.model.ts

Bu erda biz ketma-ket typcript yordamida kompaniyaning sxemasini aniqlaymiz.

"sequelize-typcript" dan {jadval, ustun, model, HasMany, PrimaryKey, AutoIncrement} import;
'./user.model' dan {User} -ni import qilish
@ Jadval ({vaqt belgilari: haqiqiy})
Eksport klassi kompaniyasi  {Modelini kengaytirmoqda

  @Column ({basicKey: true, autoIncrement: true})
  id: raqam;

  @ Ustun
  ism: satr;

  @HasMany (() => Foydalanuvchi)
  foydalanuvchilar: Foydalanuvchi [];
}

Foydalanuvchi modeli - modellar / user.model.ts

Bu erda biz foydalanuvchi modelini aniqlaymiz. Shuningdek, biz autentifikatsiya uchun ba'zi maxsus funktsiyalarni qo'shamiz.

'sequelize-typcript' dan {jadval, ustun, model, HasMany, PrimaryKey, AutoIncrement, BelongsTo, ForeignKey, BeforeSave} import;
"./company.model" dan {Company} import qilish;
import 'bcrypt' dan bcrypt sifatida;
'wait-to-js' -dan import qilish;
import qilish 'jsonwebtoken''jsonwebtoken';
'../config' dan {ENV} ni import qilish;

@ Jadval ({vaqt belgilari: haqiqiy})
eksport klassi foydalanuvchi  {Modelini kengaytiradi
  @Column ({basicKey: true, autoIncrement: true})
  id: raqam;

  @ Ustun
  firstName: satr;

  @ Ustun
  lastName: satr;

  @ Ustun
  elektron pochta: satr;

  @ Ustun
  parol: satr;

  @ForeignKey (() => Kompaniya)
  @ Ustun
  companyId: raqam;

  @BelongsTo (() => Kompaniya)
  kompaniya: kompaniya;
  jwt: satr;
  kirish: boolean;
  @BeforeSave
  statik async hashPassword (foydalanuvchi: foydalanuvchi) {
    yo'l qo'yib ber
    if (user.changed ('parol')) {
        tuz, aralashtiraylik;
        [err, tuz] = kuting (bcrypt.genSalt (10));
        agar (xato) {
          otish adash;
        }

        [err, hash] = kuting (bcrypt.hash (user.password, tuz));
        agar (xato) {
          otish adash;
        }
        user.password = hash;
    }
  }

  async solishtirishPassword (pw) {
      adashmoq, o'tkazmoq;
      if (! this.password) {
        yangi xato tashlash ('Parol yo'q');
      }

      [err, pass] = kuting (bcrypt.compare (pw, this.password));
      agar (xato) {
        otish adash;
      }

      agar (! pass) {
        'Noto'g'ri parol' ni tashlang;
      }

      buni qaytaring;
  };

  getJwt () {
      return 'Bearer' + jsonwebtoken.sign ({
          id: this.id,
      }, ENV.JWT_ENCRYPTION, {muddati tugaydi: ENV.JWT_EXPIRATION});
  }
}

Bu erda kod juda ko'p, shuning uchun agar uni buzib tashlashimni xohlasangiz, sharh bering.

Agar yaxshilash bo'yicha biron bir taklifingiz bo'lsa, iltimos, menga xabar bering! Agar muntazam javascript-da shablon yaratishni xohlasam, menga xabar bering! Bundan tashqari, agar biron bir savolingiz bo'lsa, men o'sha kuni javob berishga harakat qilaman, shuning uchun so'rashdan qo'rqmang!

Rahmat,

Brayan Shardt