Category Archives: Node.js

Redis cache ve axios örneği

Redis ANSI C ile yazılmış açık kaynak kodlu bir veri yapısı tutucusudur(data structure store). Database, cache veya publish/subscribe mesajlaşmada broker olarak kullanılabilir. Pratikte cache olarak kullanımı daha yaygındır. Cache olarak kullanımda ilişkisel veritabanından farklı olarak veriyi diskte tutmak yerine ana bellekte tutar. Sorgu RAM sonucu RAM üzerinden gelir(in-memory).

Docker ile redis kurulumu – redis default port: 6379

docker run --name redisserver -p 6379:6379 -d redis

Axios ile spesifik bir ip adresinin bilgilerini getiren apiye request atacağız. İlk request atıldığında redis devreye girecek ve ilk datayı key, value şeklinde belirtilen zaman dilimi süresince memory’de saklayacak. Sonraki aynı atılan requestlerde request atılmadan önce cache içerisinde varsa veriyi cache içerisinden getirecek. Eğer yoksa resource api çağrımı yapacak. Bu yöntem ile 300ms süreyi 15ms-20ms arasına çekmek mümkün oldu.

npm install redis axios

Request/Response caching: client.setEx ile 60 saniyeliğine datayı cache ediyoruz

const REDIS_PORT = process.env.REDIS_PORT || 6379;
const client = redis.createClient(REDIS_PORT);

router.get('/ipInfo', cache, (req, res) => {

  const { ipAddress } = req.query;
  let url = `https://iplist.cc/api/${ipAddress}`;

  axios.get(url)
  .then(function (response) {

    if(response.status == 200){
      //key, second, payload data
      let dd = JSON.stringify(response.data);
      client.setex(ipAddress, 60, dd);
      return res.json(response.data);
    }
    res.send('Fail');

  })
  .catch(function (error) {
    return res.json({
      err: error
    });
  })

});

Cache middleware

function cache(req, res, next) {
  const { ipAddress } = req.query;

  client.get(ipAddress, (err, data) => {
    if (err) throw err;

    if (data !== null) {
      console.log('redis cache');
      res.json(JSON.parse(data));
    } else {
      next();
    }

  });
}

Request/Response ölçümü

curl-format.txt

DNS lookup, TCP bağlantısı, varsa redirect, veri iletimi gibi işlerin ölçümü için format dosyası

time_namelookup: %{time_namelookup}
time_connect: %{time_connect}
time_appconnect: %{time_appconnect}
time_pretransfer: %{time_pretransfer}
time_redirect: %{time_redirect}
time_starttransfer: %{time_starttransfer}
———
time_total: %{time_total}

CURL Komutu

curl -w "@curl-format.txt" -o /dev/null -s http://localhost:3000/user/ipInfo?i pAddress=52.178.167.109

Cache öncesi

time_namelookup: 0.015000
time_connect: 0.015000
time_appconnect: 0.000000
time_pretransfer: 0.015000
time_redirect: 0.000000
time_starttransfer: 0.250000
———
time_total: 0.250000

Cache sonrası

time_namelookup: 0.000001
time_connect: 0.000001
time_appconnect: 0.000000
time_pretransfer: 0.000001
time_redirect: 0.000000
time_starttransfer: 0.015000
———
time_total: 0.015000

250ms den cache ile response süresi 15 ms oldu.

Redis bu örnekte remote bir API ye bağlandı fakat uzun süren ilişkisel db sorgularıda bu şekilde cache edilebilir

https://github.com/eroltutumlu/node-auth/blob/master/routes/user.js

Node.js ile JWT authentication örneği

Kullanılan bağımlılıklar

  • Express: HTTP metodlarını hızlı bir geliştirmemizi sağlayan Node.js framework
  • MongoDB Atlas: https://cloud.mongodb.com/ MongoDB i locale kurmak yerine cloud üzerinden kurulumunun yapılması için
  • Mongoose: MongoDB için Node.js nesne modelleme aracı (Hibernate gibi)
  • Bcrypt: Parola hash için kullanılan node.js kütüphanesi. (https://github.com/kelektiv/node.bcrypt.js) Örnekte parolayı hashlerken random text(SALT) + password şeklinde hash ediliyor.
  • Jsonwebtoken: JWT üretmemizi sağlayacak Node.js kütüphanesi
  • Body-parser: HTTP Request içerisindeki body kısmını alabilmemiz için gerekli modül

MongoDB için cloud.mongodb.com üzerinden bir cluster oluşturulur. IP Whitelist ile sadece kendiniz erişebileceğiniz şekilde ayarlayabilirsiniz yada 0.0.0.0 yaparak herkesin erişmesini sağlayabilirsiniz

Cluster->Connect->Connect your application ile ConnectionString alınır. Network access ile IP Whitelist oluşturulur.
let dbUrl = 'mongodb+srv://SUNUCU_ADI:'+ encodeURIComponent('PAROLA_BURADA') + '@cluster0-vtlge.mongodb.net/test?retryWrites=true&w=majority';
module.exports = {
    mongoURI: dbUrl
};

MongoDB dağıtık ve verileri Document-based(tablosal değil döküman tabanlı key, value gibi) tabanlı bir veritabanıdır. Mongoose ise MongoDB dökümanlarını biraz daha tablosal hale (schema based) getiren nesne modelleme aracıdır. Schema validasyonu, type casting/conversions, model logic gibi işleri kendisi yönetir. CRUD metodları mongoose ile schema ları ile birlikte gelir.

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  date: {
    type: Date,
    default: Date.now
  }
});
const User = mongoose.model('User', UserSchema);
module.exports = User;

Parolayı database e kayıt ederken plain halini değilde hash halini database’e kayıt etmek iyi bir pratiktir. Bcrypt kendi built-in metodları ile otomatik üretilen salt parolayı db ye kayıt eder. Login sırasında compare ederken yine salt kullanılarak hash edilmiş şifreyi kullanıcının şifresi ile kıyaslar.

https://codahale.com/how-to-safely-store-a-password/ —- tl;dr : use bcrypt

router.post('/register', (req, res) => {
  const { name, email, password } = req.body;
  User.findOne({ name })
    .then(user=>  {
      if(user) {return res.status(400).json()}
    });
  const newUser = new User({
    name,
    email,
    password
  });
  bcrypt.hash(password, saltRounds, (err, hash) => {
    newUser.password = hash;
    newUser.save((err, user) => {
      if (err) return console.error(err);
      console.log(user.email);
    });  
  });
  res.send("ok");
});

LogIn ve JWT üretmek/1dakikalık geçerli accessToken

router.get('/authenticate', (req, res) => {
  const {username, password} = req.query;
  User.findOne({name: username}, (err, user) => {
    if(user){
      bcrypt.compare(password, user.password, function(err, result) {
        if(result){
          jwt.sign({id: user.id, email: user.email}, 
            require('../config/default').JWT_SECRET,
            {expiresIn: '60s'},
            (err, token) => {
              return res.json({
                token: token
              });
          });
        }else{
          return res.status(400).json('user or password is incorrect')
        }
      });
    }else{
      return res.status(400).json('user or password is incorrect')
    }
  });
});

API güvenli hale getirmek: Özel bir resource’a request geldiğinde middleware fonksiyonu ile Request header’i içerisinde Authorization eki aranması ve request objesine token set edilmesi. Resource içerisinde ise token verify edilir.

router.get('/profil', verifyToken, (req, res) => {
  jwt.verify(req.token, require('../config/default').JWT_SECRET, (err, userData) => {
    if(err){
      res.sendStatus(403);
    }else{
      res.json({
        userData,
        secret: 'Node is great'
      });
    }
  });
});

function verifyToken(req, res, next){
  const bearerHeader =  req.headers['x-access-token'] || req.headers['authorization'];
  if(typeof bearerHeader !== 'undefined'){
    const bearer = bearerHeader.split(' ');
    const token = bearer[1];
    req.token = token;
    next();
  }else {
    res.sendStatus(403).json('Access denied');
  }
}

Kod: https://github.com/eroltutumlu/node-auth