Tu baza, tu baza, słyszycie mnie?

14 maja 2017 o 22:52 Autor:

Nastał ten przełomowy moment w projekcie kiedy dwa jego elementy dowiedziały się o swoim istnieniu. Część frontendowa odebrała wiadomość od części backendowej. Bardzo mnie to cieszy.

Żeby jednak nie było tak optymistycznie to powiem tylko, że wiadomość była na stałe wpisana w serwerze i jest testowa tak jak cały serwer. Jednak mimo wszystko uważam to za przełom ponieważ po 2 miesiącach pracy wysłałem zapytanie z poziomu Reacta.

Serwer

W tym momencie mój serwer jest bardzo prosty. Ma on jednak dwie ważne funkcje – pozwala wejść na dowolną podstronę bez przechodzenia przez stronę główną i umożliwia odebranie zapytania, dla którego zwróci predefiniowany błąd.

Testowy kod wygląda tak:

var express = require('express');
var path = require('path')

var server = express();
server.use(express.static(path.resolve(__dirname + '/../web/')));

server.post('/api/users/signin', function(req, res) {
  return res.status(404).json({
    error: true,
    message: "Dupa"
  })
})

server.get('/*', function(req, res){
  res.sendFile(path.resolve(__dirname + '/../web/index.html'));
});

var port = 8080;
server.listen(port, function() {
  console.log('server listening on port ' + port);
});

Backend jest napisany w NodeJS z pomocą biblioteki Express, która ułatwia budowanie aplikacji HTTP.

Linia server.use(express.static(path.resolve(__dirname + ‚/../web/’))); Odpowiada za obsługę statycznych treści. Jeśli na stronie mam jakieś obrazki to będą one szukane w podanym tutaj katalogu.

Następnie jest część odpowiedzialna za odebranie zapytania POST na wpisany adres. Funkcja dla takiego zapytania zwraca response ze statusem 404 i obiektem reprezentującym błąd. Jak widać „dupa-debugging” musi być :P

Później mam część zwracającą nasz główny plik frontendu dla dowolnego adresu w domenie (poza adresami obsłużonymi powyżej). Dzięki temu mogę wpisać w przeglądarce http://localhost/login  i od razu przejść na stronę logowania zamiast wchodzić na stronę główną i dopiero wybierać logowanie z menu. Przyśpiesza to testowanie. Będzie też obecne (zapewne w ulepszonej wersji) w ostatecznej aplikacji bo mniej więcej tego wymagają aplikacje SPA.

Na koniec jest wybranie portu na jakim będzie dział serwer i wypisanie w konsoli informacji o jego uruchomieniu.

Uderz serwer, a odpowie

W tym momencie walczę z komunikacją przy logowaniu. Korzystam z tego przykładu: https://github.com/rajaraodv/react-redux-blog/.

W skrócie – doszło sporo typów akcji. Dla samego logowania mam jednak takie trzy:

import axios from 'axios'

import * as types from '../action-types'

const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8080/api' : '/api'

//....

export function signInUser(formValues) {
    const request = axios.post(`${ROOT_URL}/users/signin`, formValues)

    return {
        type: types.SIGNIN_USER,
        payload: request
    }
}

export function signInUserSuccess(user) {
    return {
        type: types.SIGNIN_USER_SUCCESS,
        payload: user
    }
}

export function signInUserFailure(error) {
    return {
        type: types.SIGNIN_USER_FAILURE,
        payload: error
    }
}

Pierwsza rzecz jest taka, że korzystam z biblioteki axios do ajaxowych zapytań.

ROOT_URL to mój bazowy adres, różny dla aplikacji uruchomionej lokalnie i w przyszłości na serwerze.

Najciekawszą z akcji jest ta pierwsza. Tworzy ona zapytanie typu POST do serwera i przekazuje jego odpowiedź do reducera. Przy okazji poznałem kolejny element JavaScriptu. Widzicie ten string z adresem? Tam nie ma apostrofu tylko grawis (tak to nazywa Wikipedia). Dzięki temu mogę w tekście wstawiać bezpośrednio zmienne. W przykładzie powyżej widzicie jak wstawiony jest bazowy URL.

Reducer

Dla powyższych akcji mam również reducer. Tutaj pokażę tylko jego fragment obsługujący te właśnie akcje:

case types.SIGNIN_USER:
    return {...state, user: null, status: 'signin', error: null, loading: true}
case types.SIGNIN_USER_SUCCESS:
    return {...state, user: action.payload.user, status: 'authenticated', error: null, loading: false}
case types.SIGNIN_USER_FAILURE:
    error = action.payload.data || {message: action.payload.message}
    return {...state, user: null, status: 'signin', error: error, loading: false}

Dla akcji logowania ustawia on odpowiedni status użytkownika i włącza flagę mówiącą, że dane są w trakcie ładowania (w tym momencie jej nie używam).

Następnie jest przypadek kiedy udało się zalogować. Status użytkownika się zmienia, a jego obiekt jest ustawiany w state.

Ostatni z pokazanych przypadków obsługuje sytuację błędu logowania. Ustawia on wartość zwróconego błędu.

Kontener

Ostatni element, który pokażę to metoda wywoływana przy submitowaniu formularza logowania:

loginUser: function(values, dispatch) {
        dispatch(commonActions.blockUi())
        return dispatch(userActions.signInUser(values))
        .then((result) => {
            if(result.payload && result.payload.status !== 200) {
                dispatch(userActions.signInUserFailure(result.payload.data))
                dispatch(commonActions.unblockUi())
                //throw new SubmissionError(result.payload.data.message)
            } else {
                sessionStorage.setItem('jwtToken', result.payload.data.token)
                dispatch(userActions.signInUserSuccess(result.payload.data))
                store.dispatch(commonActions.unblockUi())
                browserHistory.push('/')
            }
        })
    }

Wywołuje ona najpierw akcji logowania, a kiedy ta się wykona odpowiedź jest odpowiednio obsługiwana i w zależności od statusu wywoływana jest akcja powodzenia lub błędu logowania.

W tym miejscu się zatrzymałem, walczę z wyświetlaniem zwróconego błędu na ekranie.

 

Cały aktualny kod (poza serwerem, który mam w tym momencie w złym katalogu) dostępny jest na GitHubie.

 

 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *