0

I started using ApolloClient and i must say, the documentation is horrible.

My application will use GraphQL, but the login needs to be done using a simple POST request to /login providing the username and password, as formdata.

And man, getting that to work has been a complete nightmare.

Since i have to use apollo-link-rest i have to first build my client:

import ApolloClient from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'
import { createHttpLink } from 'apollo-link-http'
import { onError } from 'apollo-link-error'
import { RestLink } from 'apollo-link-rest'

const cache = new InMemoryCache()

const httpLink = new createHttpLink({
  credentials: 'include',
})

const restLink = new RestLink({
  endpoints: {
    localhost: {
      uri: "http://localhost:8080"
    }
  },
  credentials: 'include'
})

const errorLink = onError(({ graphQLErrors, networkError, operation }) => {
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, locations, path }) => {
      console.log(`[GraphQL error] Message: ${message}, ${path}`)
    })
  }
  if (networkError) {
    console.log(
      `[Network error ${networkError}] Operation: ${operation.operationName}`
    )
  }
})

export const client = new ApolloClient({
  link: ApolloLink.from([errorLink, restLink, httpLink]),
  cache,
})

then i have my query:

const LOGIN_QUERY = gql`
  mutation doLogin($username: String!, $password: String!, $formSerializer: any) {
    user(input: { username: $username, password: $password})
      @rest(
        path: "/login"
        type: "user"
        method: "POST"
        endpoint: "localhost"
        bodySerializer: $formSerializer
      ) {
        id
        name
      }
  }`

const formSerializer = (data, headers) => {
  const formData = new URLSearchParams()
  for (let key in data) {
    if (data.hasOwnProperty(key)) {
      formData.append(key, data[key])
    }
  }

  headers.set('Content-Type', 'application/x-www-form-urlencoded')

  return { body: formData, headers }
}

I find it utterly crazy that the documentation doesn't have an example of how you send form data, i had to google a lot until i found out how to do it.

and i execute the query in a login component:

  const [doLogin, { data }] = useMutation(LOGIN_QUERY, {
    onCompleted: () => {
      console.log("Success")
    },
    onError: () => {
      console.log("Poop")
    }
  })

  const handleSubmit = (event) => {
    event.preventDefault()
    console.log('logging in...')
    doLogin({
      variables: { username, password, formSerializer },
    })
  }

the server logs me in, returns a session cookie, and a body containing:

{
    "data": {
        "user": {
            "id": 1
            "name": "Foobar"
        }
    }
}

When i check the cache state using the ApolloClient google chrome plugin, the cache has:

user:null
    id: null
    name: null

so here are my questions:

  1. Do i need to set a default state?
  2. Documentation only show mutations on objects that first have been queried upon, can i only update the cache using a mutation if the object already exists in the cache?
  3. Do i need to write resolvers for every type?
  4. And ofc, why do i get null values?
  5. Does all data need to be returned wrapped in a data object, even for rest queries?
  6. Must the formSerializer be passed as a variable to the query, because i find that super ugly.

As said before... the ApolloClient documentation is one of the worst documentations i have ever read. Not user friendly at all.

glinda93
  • 7,659
  • 5
  • 40
  • 78
Toerktumlare
  • 12,548
  • 3
  • 35
  • 54
  • What does `useEffect(() => { if(!data) return; console.log(data) }, [data]);` say? Does that return null values too? – glinda93 Jul 12 '20 at 12:49
  • yes i am getting null values there too `user: id: null name: null __typename: "user"` – Toerktumlare Jul 12 '20 at 12:58
  • Seeing this [github comment](https://github.com/apollographql/apollo-client/issues/3030#issuecomment-448472845), and the [doc](https://www.apollographql.com/docs/link/links/rest/#response-transforming) I think your problem is server is wrapping data with unnecessary `data` key. – glinda93 Jul 12 '20 at 13:08
  • Implement your custom responseTransformer. Show me if you've done it already. – glinda93 Jul 12 '20 at 13:08
  • since i control the backend i removed the `data` wrapper and the server now returns `{ "user": { "id": 1, "name": "Foobar" }}` and there is no change, still getting null values. – Toerktumlare Jul 12 '20 at 13:14
  • If you have the backend control, why do you want to implement login with REST? Graphql mutations can handle login pretty well. :( – glinda93 Jul 12 '20 at 13:18
  • because i am using Spring Security on a java backend, the backend is not node, Spring security does not have graqhql mutation login support, and i dont want to write a full custom graphql backend security solution. – Toerktumlare Jul 12 '20 at 13:20
  • and i managed to solve it now, https://www.apollographql.com/docs/link/links/rest/#response-transforming the documentation states that it is expecting the data to come at the root, so i needed to remove `user` too, and pass the data back as plain `{ "id": 1, "name": "Foobar" }` then i actually managed to resolve it. The Apollo documentation is HORRIBLE, and any sane client would cast a "de/serializing" error if it couldn't parse the returned data correctly... but not ApolloClient, it just silently sets null values – Toerktumlare Jul 12 '20 at 13:23
  • 1
    Read response transforming doc carefully. Apollo expects `{ "id": 1, "name": "Apollo" }` I think – glinda93 Jul 12 '20 at 13:23
  • 1
    Ha, what a sync – glinda93 Jul 12 '20 at 13:24
  • write it as an answer Bravemaster and i\ll credit you, it was nice of you to lead me on to the right path – Toerktumlare Jul 12 '20 at 13:24
  • OK, man. I'd love to help java developers. Esp - spring – glinda93 Jul 12 '20 at 13:25

1 Answers1

1

According to Response transforming, apollo expects response from REST APIs like the following:

{
  "id": 1,
  "name": "Apollo"
}

If you cannot control backend, you can use customized response transformer:

responseTransformer: async response => response.json().then(({data}) => data.data?.user)
glinda93
  • 7,659
  • 5
  • 40
  • 78