Jak zamockować zapytania i mutacje GraphQL

GraphQL pozwala programistom frontendowym szybko makietować przykładowe dane i przełączać się na rzeczywiste dane, gdy backend jest gotowy. W tym samouczku pokazuję, jak mockować pola, zapytańia i mutacjie po stronie klienta

W dzisiejszych czasach rozproszone architektury oprogramowania stały się bardziej popularne, a wraz z tym trendem zespoły programistyczne stosują podejście API-first do tworzenia produktów.

Z drugiej strony, architekci i deweloperzy coraz częściej decydują się na korzystanie z GraphQL API zamiast REST (ale REST API są nadal dobre)

Nie będę tutaj przytaczał dobrze znanych zalet i wad GraphQL , ale chcę zwrócić uwagę na tę, która nie jest dobrze znana, ale jest dość ważna dla programistów:

Jedną z najważniejszych zalet korzystania z GraphQL jest to, że programista frontendowy może szybko zamockować przykładowe dane i przełączyć się na prawdziwe dane, gdy backend jest gotowy.

Czym dokładnie są mockupy danych?

Załóżmy, że jakaś funkcjonalność potrzebuje danych z backendu lub w jakiś sposób musi komunikować się z API, a dane te nie są dostępne lub API nie zostało jeszcze wykonane. W takim przypadku programista Front-end musi przygotować przykładowe dane. Spójrz na poniższe przykłady:

1. Wyświetlanie dodatkowych informacji o produkcie

Zespół programistów pracuje nad wyświetlaniem dodatkowych informacji o produkcie na stronie produktu. Dane te nazywane są „kluczowymi cechami” i składają się z obrazu, nazwy i opisu. Dane te będą pochodzić z backendu. Zespół backendowy nie rozpoczął jeszcze pracy nad tą funkcjonalnością, więc programista frontendowy decyduje się na makietę tych danych i wyświetlenie tej makiety na frontendzie. Gdy backend zostanie ukończony, dane makiety zostaną zastąpione prawdziwymi danymi.

2. Wysyłanie wiadomości do sprzedawcy

Ta funkcja umożliwia klientom wysyłanie wiadomości do sprzedawcy. Klient wypełnia formularz. Wpisuje swoje imię, nazwisko, adres e-mail i wiadomość. Ponadto muszą zaakceptować zgodę na przetwarzanie danych osobowych. Programista frontendowy zbudował już formularz i walidację i jest na etapie wysyłania formularza do backendu. Backend musi odebrać formularz i zwrócić komunikat o powodzeniu lub błędzie, który zostanie wyświetlony użytkownikowi. Część backendowa nie jest jeszcze gotowa, więc programista Frontend musi zamockować interakcję z backendem.

Podsumowując, gdy niektóre dane, takie jak pola lub nawet kolekcje danych, nie zostały jeszcze zaimplementowane w backendzie, programiści używają fałszywych wartości (mock data) i zastępują je prawdziwymi danymi, gdy backend jest gotowy.

W tym artykule pokażę, jak to zrobić:

  • pojedyncze pola
  • zapytania
  • mutacja

Na koniec pokażę ci, jak łatwo zastąpić pozorowane dane prawdziwymi danymi z backendu.

Dzięki tej wiedzy będziesz w stanie pracować nad front-endem bardziej efektywnie, nawet jeśli back-end pozostaje daleko w tyle.

Wymagania wstępne

Komputer z edytorem tekstu, NodeJS, połączenie internetowe, podstawowe umiejętności JavaScript, React i GraphQL .

Tworzenie aplikacji react

Używam create-react-app, aby stworzyć rusztowanie dla nowego projektu:

$ npx create-react-app graphql-mocks
$ cd graphql-mocks
$ npm start

Instalacja klienta Apollo

Następnie używam wiersza poleceń do zainstalowania klienta apollo:

$ npm install @apollo/client graphql

Klient Apollo umożliwia łączenie się z serwerem GraphQL i wykonywanie operacji GraphQL, takich jak zapytania i mutacje, dzięki niestandardowym hakom React , takim jak useQuery lub useMutation.

Inicjalizacja klienta Apollo

Po zainstalowaniu klienta mogę połączyć mój front-end z GraphQL API. Tym razem wykorzystam publicznie dostępne API GraphQL SpaceX.

Najpierw importuję Apolo Client, InMemoryCache i ApolloProvider z @apollo/client.

import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';

Po drugie, inicjalizuję klienta:

const client = new ApolloClient({
    uri: 'https://api.spacex.land/graphql/',
    cache: new InMemoryCache(),
});

Jest to minimalna konfiguracja potrzebna do uruchomienia klienta Apollo – adres URL do API i zainicjowana pamięć podręczna w pamięci.

Po trzecie, opakowuję komponent App przez ApolloProvider:

<ApolloProvider client={client}>
  <App />
</ApolloProvider>

ApolloProvided przyjmuje jeden argument: klienta, którego już zainicjowaliśmy. Po owinięciu komponentu App za pomocą ApolloProvider, mogę używać klienta w komponencie App i każdym elemencie podrzędnym komponentu App.

Jak makietować pojedyncze pola

W tym przykładzie pokażę, jak pobrać istniejące pola z API i jak dodać pole, które nie istnieje w odpowiedzi.

Tworzenie komponentu missions

Na początku tworzę komponent Missions, który będzie odpowiedzialny za wyświetlanie misji.

Utwórz plik components \ missions \ Missions.jsx:

 export const Missions = () => {
    return <>
        <h1>Missions</h1>
        Missions will be listed here
    </>
}

Utwórz plik components \ missions \ index.js:

export { Missions } from './Missions';

Utwórz plik icomponents \ index.js:

export { Missions } from './missions';

Zaimportuj komponent Missions w pliku App.js:

import { Missions } from "./components";

Renderowanie komponentu:

function App() {
  return <main className="container">
    <Missions/>
  </main>
}

Dodaj style do App.css:

.container {
  max-width: 1200px;
  margin: 0 auto;
}

Komponent w przeglądarce powinien wyglądać następująco:

samouczek mock api

Zapytanie o dane

Chcę pobierać misje z serwera graphql. Aby to zrobić, muszę zdefiniować zapytanie.

Dodaję plik components \ missions \ missions.gql.js:

from „@apollo/client”; export default gql`{ missions(limit: 10) { description id manufacturers name twitter website wikipedia } } `;’ style=”color:#e1e4e8;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>.
import {gql} from "@apollo/client";

export default gql`{
  missions(limit: 10) {
    description
    id
    manufacturers
    name
    twitter
    website
    wikipedia
  }
}
`;

Importuję zapytanie i hook useQuery w komponentach \ Missions \ missions.jsx:

import {useQuery} from "@apollo/client";
import MISSIONS_QUERY from './missions.gql.js'

Używam hooka useQuery do pobierania danych graphql:

const {loading, error, data} = useQuery(MISSIONS_QUERY);

Dodaję trochę logiki do obsługi stanu ładowania i błędów:

if (loading) return null;
if (error) return `Error! ${error}`
if (!data?.missions?.length) {
    return 'No missions found';
}

const {missions} = data;

Na koniec renderuję dane zwrócone z API:

`}>{manufacturer}</li>)} </ol> <h3>Linki:</h3> <ul> {mission.twitter?.length && <li><a href={mission.twitter}>{mission.twitter}</a></li> } {mission.website?.length && <li><a href={mission.website}>{mission.website}</a></li> } {mission.wikipedia?.length && <li><a href={mission.wikipedia}>{mission.wikipedia}</a></li> } </ul> </div> })}’ style=”color:#e1e4e8;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>.
{missions.map(mission => {
    return <div className="mission" key={mission.id}>
        <h2>{mission.name}</h2>
        <p>{mission.description}</p>
        <h3>Manufacturers:</h3>
        <ol>
            {mission.manufacturers?.map(manufacturer => <li key={`${mission.id}-${manufacturer}`}>{manufacturer}</li>)}
        </ol>
        <h3>Links:</h3>
        <ul>
            {mission.twitter?.length && <li><a href={mission.twitter}>{mission.twitter}</a></li> }
            {mission.website?.length && <li><a href={mission.website}>{mission.website}</a></li> }
            {mission.wikipedia?.length && <li><a href={mission.wikipedia}>{mission.wikipedia}</a></li> }
        </ul>
    </div>
})}

Kompletny kod komponentu wygląda następująco:

import {useQuery} from "@apollo/client";
import MISSIONS_QUERY from './missions.gql.js'

export const Missions = () => {
    const {loading, error, data} = useQuery(MISSIONS_QUERY);

    if (loading) return null;
    if (error) return `Error! ${error}`
    if (!data?.missions?.length) {
        return 'No missions found';
    }

    const {missions} = data;

    return <>
        <h1>Missions</h1>
        {missions.map(mission => {
            return <div className="mission" key={mission.id}>
                <h2>{mission.name}</h2>
                <p>{mission.description}</p>
                <h3>Manufacturers:</h3>
                <ol>
                    {mission.manufacturers?.map(manufacturer => <li key={`${mission.id}-${manufacturer}`}>{manufacturer}</li>)}
                </ol>
                <h3>Links:</h3>
                <ul>
                    {mission.twitter?.length && <li><a href={mission.twitter}>{mission.twitter}</a></li> }
                    {mission.website?.length && <li><a href={mission.website}>{mission.website}</a></li> }
                    {mission.wikipedia?.length && <li><a href={mission.wikipedia}>{mission.wikipedia}</a></li> }
                </ul>
            </div>
        })}
    </>
}

W przeglądarce powinny pojawić się wyrenderowane dane:

Rozszerzenie schematu GraphQL po stronie klienta

Tutaj zaczyna się główny temat artykułu! Komponent Missions renderuje niektóre dane o misjach z przodu, takie jak opis, nazwa, linki internetowe itp:

schemat zdefiniowany w graphql

Chcę zamockować jedno pole o nazwie: sponsor. Pole to jest tablicą. Aby to zrobić, tworzę plik graphql-type-defs.js w katalogu głównym projektu z następującą zawartością:

from „@apollo/client”; export default gql` extend type Mission { sponsors: [String] } `;’ style=”color:#e1e4e8;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>.
import {gql} from "@apollo/client";

export default gql`
    extend type Mission {
        sponsors: [String]
    }
`;

Ta definicja schematu rozszerza typ Mission i dodaje do niego pole sponsors.

Teraz importuję definicję typu w pliku index.js:

import typeDefs from './graphql-type-defs';

I przekazuję go do konstruktora ApolloClient:

const client = new ApolloClient({
    uri: 'https://api.spacex.land/graphql/',
    cache: new InMemoryCache(),
    typeDefs
});

Definiowanie funkcji odczytu z makietą danych

Następnym krokiem jest zdefiniowanie niestandardowej funkcji odczytu, która wygeneruje dla nas mockowane dane.

Zanim to zrobię, instaluję FakerJS. jest to biblioteka (generator fałszywych danych), która pomaga tworzyć fałszywe i losowe dane.

$ npm install @faker-js/faker --save-dev

Następnie przekazuję konfigurację z politykami typów obiektów do konstruktora InMemoryCache:

cache: new InMemoryCache({
    typePolicies: {
        Mission: {
            fields: {
                sponsors: {
                    read() {
                        return [...faker.random.words(faker.datatype.number({
                            'min': 1,
                            'max': 5
                        })).split(' ')]
                    }
                },
            },
        },
    },
}),

Kod ten definiuje funkcję read() dla pola sponsorów typu Mission. Funkcja read() zwraca fałszywe obiekty. W tym przypadku zwraca nową tablicę zawierającą od jednego do pięciu elementów. Elementami tej tablicy są losowe słowa.

Zapytanie z dyrektywą @client i wyświetlenie danych

Aby pobrać pole mock, muszę dodać je do zapytania. Aby to zadziałało, muszę użyć dyrektywy @client. Zapoznaj się ze zaktualizowanym zapytaniem o misje:

export default gql`{
  missions(limit: 10) {
    description
    id
    manufacturers
    name
    twitter
    website
    wikipedia
    sponsors @client // here you go
  }
}
`;

Wreszcie mogę wyrenderować pole sponsorów na interfejsie użytkownika. Dodaję ten kod do funkcji renderowania komponentu misji:

<h3>Sponsors:</h3>
<ol>
    {mission.sponsors?.map(sponsor => <li key={`${mission.id}-${sponsor}`}>{sponsor}</li>)}
</ol>

Wyniki w przeglądarce:

makiety obiektów na ekranie

Jak wykonać makietę całego zapytania

Mockowanie pojedynczych pól jest bardzo przydatne. Co więcej, czasami deweloperzy chcą zakpić z zapytania lub mutacji, która nie istnieje w backendzie. Zacznijmy od wyśmiewania zapytania.

Dodanie nowego zapytania do schematu

Dodajmy zapytanie publications, które zwraca tablicę publikacji (nazwa publikacji i adres URL).

Rozszerzam plik graphql-type-defs.js dodając nowe typy:

type Query {
    publications: [Publication]
}
  
type Publication {
    name: String!
    url: String!
 }

Definiowanie resolvera

Następnie muszę zdefiniować resolver, który będzie generował fałszywe dane dla zapytania o publikacje.

Tworzę plik graphql-resolvers.js:

import {faker} from "@faker-js/faker";

export default {
    Query: {
        publications: () => {
            const publications  = [];
            const publicationLength = faker.datatype.number({
                'min': 1,
                'max': 5
            })

            for (let i = 0; i < publicationLength; i++) {
                publications.push({
                    name: faker.lorem.sentence(),
                    url: faker.internet.url()
                })
            }
            return publications;
        },
    }
}

Zdefiniowałem funkcję publications, która zwraca nową tablicę fałszywych publikacji.

Rejestrowanie resolvera

Aby zarejestrować resolver, należy przekazać go do konstruktora ApolloClient:

import resolvers from './graphql-resolvers';

const client = new ApolloClient({
    uri: 'https://api.spacex.land/graphql/',
    cache: new InMemoryCache({
        typePolicies: {
            Mission: {
                fields: {
                    sponsors: {
                        read() {
                            return [...faker.random.words(faker.datatype.number({
                                'min': 1,
                                'max': 5
                            })).split(' ')]
                        }
                    },
                },
            },
        },
    }),
    typeDefs,
    resolvers // here you go
});

Używanie makiety zapytania w aplikacji

Stwórzmy komponent publikacji, który wyświetla wyśmiewane dane.

components \ publications \ Publications.jsx

import {useQuery} from "@apollo/client";
// 1. here is imported the publications query
import PUBLICATIONS_QUERY from './publications.gql.js'

export const Publications = () => {
   // 2. here the query is used
   const {loading, error, data} = useQuery(PUBLICATIONS_QUERY);


    if (loading) return null;
    if (error) return `Error! ${error}`;
    if (!data?.publications?.length) {
        return 'No publications found';
    }

    const {publications} = data;

    return <>
        <h1>Publications</h1>
        <ol>
            {publications?.map(publication => <li key={publication.name}><a href={publication.url}>{publication.name}</a></li>)}
        </ol>
    </>
}

(1.) W to miejsce zaimportowałem zapytanie o publikacje, a tutaj (2.) użyłem w nim haka useQuery.

Jak widać z perspektywy komponentu i hooka useQuery, nie ma znaczenia, czy używany kamieniołom jest fałszywy czy prawdziwy. Jest przejrzysty i działa w ten sam sposób.

components \ publications \ publications.gql.js:

from „@apollo/client”; export default gql` { publications @client { name url } } `;’ style=”color:#e1e4e8;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>.
import {gql} from "@apollo/client";

export default gql`
    {
        publications @client {
            name
            url
        }
    }
`;

Dyrektywa @client umożliwia definiowanie nie tylko pól, jak w poprzednim przykładzie, ale także zapytań i mutacji.

components \ publications \ index.js:

export {Publications} from './Publications'

components \ index.js:

export { Publications } from './publications'; // added this import
export { Missions } from './missions';

Dodaj komponent publikacji w pliku App.js:

import './App.css';
import {Missions, Publications} from "./components"; // added import

function App() {
  return <main className="container">
    <Missions/>
    <Publications/> !<-- added component -->
  </main>
}

export default App;

Wyniki w przeglądarce:

makieta obiektu na ekranie

Jak zamockować mutację GraphQL

Ostatnim przykładem, który chcę pokazać, jest sposób wyśmiewania mutacji graphql. Zaimplementujmy prosty formularz, który pozwoli użytkownikom przesłać nową publikację. Formularz zawiera dwa dane wejściowe: tytuł publikacji i jej adres URL.

komponenty ui formularza

Komponent PublicationForm

Utwórz plik components \ PublicationForm \ PublicationForm.jsx

import {useCallback, useState} from "react";

export const PublicationForm = () => {
    const [ title, setTitle ] = useState('');
    const [ url, setUrl ] = useState('');

    const submitForm = useCallback((e) => {
        e.preventDefault();
        console.log(title, url);
    }, [title, url]);
    return <form onSubmit={submitForm}>
        <legend>Submit a new publication:</legend>
        <input type="text" placeholder="Publication title" value={title} onChange={(e) => setTitle(e.target.value)}/>
        <input type="text" placeholder="Publication URL" value={url} onChange={(e) => setUrl(e.target.value)}/>
        <button type="submit">Submit</button>
    </form>
}

W formularzu są więc dwa pola i przycisk przesyłania. Gdy użytkownik kliknie przycisk submit, wywoływana jest funkcja submitForm. Na razie loguje tylko do konsoli.

Utwórz plik components \ publicationForm \ index.js

export { PublicationForm } from './PublicationForm';

Ponownie wyeksportuj komponent w components/index.js:

export { Publications } from './publications';
export { Missions } from './missions';
export { PublicationForm } from './publicationForm'; // added here

Dodaj komponent do funkcji renderowania komponentu aplikacji:

import './App.css';
import {Missions, Publications, PublicationForm} from "./components";

function App() {
  return <main className="container">
    <Missions/>
    <Publications/>
    <PublicationForm/>
  </main>
}

export default App;

Dodanie mutacji do schematu

Zdefiniujmy nową mutację w naszym schemacie graphql:

type Mutation {
  addPublication(name: String!, url: String!): String
}

Ostateczna wersja graphql-type-defs wygląda następująco:

from „@apollo/client”; export default gql` extend type Mission { sponsors: [String] } type Query { publications: [Publication] } type Mutation { addPublication(name: String!, url: String!): String } type Publikacja { name: String! url: String! } `;’ style=”color:#e1e4e8;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>.
import {gql} from "@apollo/client";

export default gql`
    extend type Mission {
        sponsors: [String]
    }
    
    type Query {
        publications: [Publication]
    }
    
    type Mutation {
      addPublication(name: String!, url: String!): String
    }
  
    type Publication {
        name: String!
        url: String!
    }
`;

Definiowanie resolvera dla mutacji

Teraz dodam resolver mutacji addPublication do pliku graphql-resolvers.js:

import {faker} from "@faker-js/faker";

export default {
    Query: {
        // query resolvers
    },

    Mutation: {
        addPublication: (parent, args, context, info) => {
            console.log(parent, args, context, info);
            return 'Your publication has been submitted, thank you!'

        }
    }
}

Zdefiniowałem mutację i zwraca ona ciąg znaków. Oczywiście, jeśli potrzebujesz bardziej zaawansowanego testowania wyśmiewanej mutacji, możesz dodać kod tutaj.

Jak używać wyśmiewanej mutacji w aplikacji

Dodaj plik components \ publicationForm \ addPublication.gql.js:

from „@apollo/client”; export default gql` mutation addPublication( $name: String!, $url: String! ) { addPublication( name: $name, url: $url ) @client } `;’ style=”color:#e1e4e8;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>.
import {gql} from "@apollo/client";

export default gql`
    mutation addPublication(
        $name: String!,
        $url: String!
    ) {
        addPublication(
            name: $name,
            url: $url
        ) @client
    }
`;

Jak widać, tutaj również użyłem dyrektywy @client do zdefiniowania mock mutacji

Zaktualizuj kod komponentu publicationForm.jsx:

; }, [name, url, addPublication]); const results = useMemo(() =<encoded_tag_closed /> { return data?.addPublication }, [data]); if (loading) return null; if (error) return `Error! $<wpml_curved wpml_value=’error’></wpml_curved>`; return <encoded_tag_open />form onSubmit=<wpml_curved wpml_value=’submitForm’></wpml_curved><encoded_tag_closed /> <encoded_tag_open />legend<encoded_tag_closed />Submit a new publication:<encoded_tag_open />/legend<encoded_tag_closed /> <encoded_tag_open />input type=”text” placeholder=”Publication title” value=<wpml_curved wpml_value=’name’></wpml_curved> onChange={(e) =<encoded_tag_closed /> setName(e.target.value)}/<encoded_tag_closed /> <encoded_tag_open />input type=”text” placeholder=”Publication URL” value=<wpml_curved wpml_value=’url’></wpml_curved> onChange={(e) =<encoded_tag_closed /> setUrl(e.target.value)}/<encoded_tag_closed /> <encoded_tag_open />button type=”submit”<encoded_tag_closed />Submit<encoded_tag_open />/button<encoded_tag_closed /> <encoded_tag_open />div<encoded_tag_closed /><wpml_curved wpml_value=’results’></wpml_curved><encoded_tag_open />/div<encoded_tag_closed /> <encoded_tag_open />/form<encoded_tag_closed /> }” style=”color:#e1e4e8;display:none” aria-label=”Copy” class=”code-block-pro-copy-button”>
import {useCallback, useMemo, useState} from "react";
import {useMutation} from "@apollo/client";
import ADD_PUBLICATION_MUTATION  from './addPublication.gql';

export const PublicationForm = () => {
    const [ name, setName ] = useState('');
    const [ url, setUrl ] = useState('');
    const [addPublication, {data, loading, error}] = useMutation(ADD_PUBLICATION_MUTATION)

    const submitForm = useCallback((e) => {
        e.preventDefault();
        addPublication({variables: {name, url >;
    }, [name, url, addPublication]);

    const results = useMemo(() => {
        return data?.addPublication
    }, [data]);

    if (loading) return null;
    if (error) return `Error! ${error}`;

    return <form onSubmit={submitForm}>
        <legend>Submit a new publication:</legend>
        <input type="text" placeholder="Publication title" value={name} onChange={(e) => setName(e.target.value)}/>
        <input type="text" placeholder="Publication URL" value={url} onChange={(e) => setUrl(e.target.value)}/>
        <button type="submit">Submit</button>
        <div>{results}</div>
    </form>
}

Tutaj dodałem logikę odpowiedzialną za wykonywanie mutacji. Jeśli chodzi o zapytania – działają one tak samo z wyśmiewanymi mutacjami, jak z prawdziwymi.

Wyniki w przeglądarce:

fałszywe graphql api

Jak korzystać z danych na żywo, gdy są gotowe

Ok, więc zakpiliśmy z niektórych pól, zapytań i mutacji, i możesz zapytać, co powinieneś zrobić, gdy zespół backendowy zaimplementuje wszystkie żądane pola i operacje w API.

To całkiem proste. Powinieneś:

  1. 1. usuwanie adnotacji @client – gdy określone pole lub operacja jest gotowa, wystarczy usunąć dyrektywę @client z zapytania/mutacji.
  2. 2. usuń resolvery klienta – usuń resolvery, ponieważ nie są już potrzebne, gdy dane są wypełniane z API
  3. 3. usunięcie definicji typu klienta – to samo tutaj, schemat powinien być zaimplementowany po stronie backendu, więc nie jest już potrzebny.

Podsumowanie

W tym artykule pokazałem, jak kpić z zapytań i mutacji GraphQL. W porównaniu do API REST, wyśmiewanie zapytań GraphQL jest znacznie łatwiejsze. Późniejsze przejście na rzeczywiste dane wiąże się jedynie ze zmianą zapytań GraphQL i usunięciem resolverów. Moim zdaniem mockowanie danych w GraphQL jest znacznie łatwiejsze niż w REST, co jest niewątpliwie korzystne dla wszystkich.

Jeśli chcesz zamockować niekte pola lub operacje po stronie klienta za pomocą klienta Apollo, wykonaj następujące kroki:

  1. Stwórz schemat GraphQL po stronie klienta
  2. Zdefiniuj niestandardowe funkcje resolver/read
  3. Użyj dyrektywy @client w zapytaniach/mutacjach

Mam nadzieję, że spodobał Ci się ten artykuł. Dzięki za przeczytanie!

Udostępnij post:

Możesz także polubić

Kariera w branży technologicznej: Jak rozwijać swoje umiejętności

Jesteś programistą i chciałbyś się rozwijać? W internecie znajdziesz pełno materiałów o tym, jak to zrobić. Pomimo tego nie uciekaj — mam coś, co Cię zaciekawi. Czy wiesz, że Adam Małysz — legendarny polski skoczek, zanim został mistrzem latania, to był dekarzem? Nie śmiem się porównywać z Panem Adamem, natomiast są dwie rzeczy, które nas łączą.

Ja też byłem dekarzem i też udało mi się przebranżowić. Może nie w tak spektakularny sposób, ale jednak. W tym artykule podzielę się z Tobą moim osobistym doświadczeniem, które zdobyłem na drodze od dekarza przez programistę do tech leada i dam Ci wskazówki, które będziesz mógł zastosować, aby się rozwijać i awansować, a może nawet zmienić diametralnie swoją karierę.

Czytaj więcej
AHA stack przywróćmy prostotę frontendu

AHA! Przywróćmy prostotę Frontendu

Czy zastanawiałeś się, dlaczego w dzisiejszych czasach, gdy mamy dostęp do najnowszych technologii i rozwiązań, projekty IT nadal kończą się fiaskiem? Czy nie uważasz, że w wielu przypadkach zamiast upraszczać to komplikujemy sobie życie i pracę? Czasami mniej znaczy więcej, zwłaszcza w świecie frontendu! Czytaj dalej i dowiedz się czym jest AHA stack i jak robić frontend prościej.

Czytaj więcej