I'm building an isomorphic/universal React + Redux + Express app. My server-side data fetching is pretty standard: I see what routes match the URL, call the relevant data-fetching methods that all return promises, then wait for them to resolve and render the HTML.
My challenge: Some of the API calls require auth. Logged-in users have a cookie, which of course gets sent with every request. But when I'm doing data fetching on the server side to populate the store for initial render, the cookie isn't available to my API calls. How do I make that happen?
// server-entry.jsx
app.get('*', (req, res) => {
const store = createStore(
combineReducers({
// lots of reducers
}),
{},
applyMiddleware(thunk),
);
/*
The contents of getDataFetchers isn't important. All you need
to know is it returns an array of data-fetching promises like so:
dispatch(thunkAction());
*/
const fetchers = getDataFetchers(req.url, store);
Promise.all(fetchers).then(() => {
// render the tree
});
});
// one of my thunk actions hits an API endpoint looking like this:
app.get('/api', (req, res) => {
// this is simplified, but you get the idea:
// we need access to the cookie to authenticate
// but when this call is made server-side, req.session.user doesn't exist
if (!req.session.user) res.status(403);
else res.json({ data: 'here' });
});
I guess I could grab the cookie from req
in app.get
and pass is through as an argument all the way to the actual data fetch (done with axios), where it would be specified with the Cookie
header. But that feels gross.
What you suggested is correct, if you'd like your server-side HTTP requests to contain the original request's cookies, you need to construct your HTTP client (axios in your case) in a way that allows it to have access to headers either on instantiation or before making each request.
Here is a link to how Apollo does it.
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import Express from 'express';
import { StaticRouter } from 'react-router';
import { InMemoryCache } from "apollo-cache-inmemory";
import Layout from './routes/Layout';
// Note you don't have to use any particular http server, but
// we're using Express in this example
const app = new Express();
app.use((req, res) => {
const client = new ApolloClient({
ssrMode: true,
// Remember that this is the interface the SSR server will use to connect to the
// API server, so we need to ensure it isn't firewalled, etc
link: createHttpLink({
uri: 'http://localhost:3010',
credentials: 'same-origin',
headers: {
cookie: req.header('Cookie'),
},
}),
cache: new InMemoryCache(),
});
const context = {};
// The client-side App will instead use <BrowserRouter>
const App = (
<ApolloProvider client={client}>
<StaticRouter location={req.url} context={context}>
<Layout />
</StaticRouter>
</ApolloProvider>
);
// rendering code (see below)
});
As you can see, the ApolloClient
is created and passed the initial request's cookies on instantiation.