mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
add nextjs 8 jwt sample app (#1943)
This commit is contained in:
parent
44d6bb8b7e
commit
39b623f715
@ -0,0 +1,130 @@
|
||||
# nextjs-8-jwt
|
||||
|
||||
> Boilerplate to get started with Next.js 8 Serverless Mode and JWT Authentication, Hasura GraphQL engine as CMS and postgres as database.
|
||||
|
||||
## Deploy Hasura
|
||||
- Deploy Postgres and GraphQL Engine on Heroku:
|
||||
|
||||
[![Deploy to heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku)
|
||||
|
||||
Please checkout our [docs](https://docs.hasura.io/1.0/graphql/manual/deployment/index.html) for other deployment methods
|
||||
|
||||
- Get the Heroku app URL (say `my-app.herokuapp.com`)
|
||||
|
||||
### Create the initial tables
|
||||
1. Add your heroku URL in `hasura/config.yaml`
|
||||
|
||||
```yaml
|
||||
endpoint: https://<hge-heroku-url>
|
||||
```
|
||||
|
||||
2. Run `hasura migrate apply` and `hasura metadata apply` inside `hasura` directory to create the required tables and permissions for the app.
|
||||
|
||||
## Deploy JWT Server
|
||||
|
||||
- Deploy the JWT server by following the instructions [here](https://github.com/hasura/graphql-engine/tree/master/community/boilerplates/auth-servers/passportjs-jwt-roles)
|
||||
*Note* Skip the `knex migrate` step as the required tables have already been created in the above step.
|
||||
|
||||
Ensure to configure Hasura GraphQL Engine with the environment variables `HASURA_GRAPHQL_ADMIN_SECRET` and `HASURA_GRAPHQL_JWT_SECRET` as given in the above repo.
|
||||
|
||||
## Run the Next.js App
|
||||
|
||||
- Clone this repo:
|
||||
```bash
|
||||
git clone https://github.com/hasura/graphql-engine
|
||||
cd graphql-engine/community/sample-apps/nextjs-8-serverless/with-apollo-jwt
|
||||
```
|
||||
|
||||
- Install npm modules:
|
||||
```bash
|
||||
cd app
|
||||
yarn install
|
||||
```
|
||||
|
||||
- Open `lib/init-apollo.js` and configure Hasura's GraphQL Endpoint as follows:
|
||||
|
||||
```js
|
||||
const httpLink = new HttpLink({
|
||||
uri: 'https://myapp.herokuapp.com/v1alpha1/graphql', // Server URL (must be absolute)
|
||||
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
|
||||
})
|
||||
```
|
||||
Replace the `uri` with your own Hasura GraphQL endpoint.
|
||||
|
||||
We are using an Auth Middleware to inject the Authorization headers into the GraphQL requests.
|
||||
|
||||
```js
|
||||
const authMiddleware = new ApolloLink((operation, forward) => {
|
||||
// add the authorization to the headers
|
||||
operation.setContext({
|
||||
headers: {
|
||||
authorization: getToken(),
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Let's also configure the JWT server URL used for authentication.
|
||||
|
||||
Open `server.js` and update the following appropriately.
|
||||
```
|
||||
const target = 'http://localhost:8080'
|
||||
```
|
||||
|
||||
In this example, we integrate Apollo with Next by wrapping our *pages/_app.js* inside a higher-order component HOC. Using the HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application.
|
||||
|
||||
On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](https://www.apollographql.com/docs/react/features/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
|
||||
|
||||
- We have defined the graphql query in `components/ArticleList.js`.
|
||||
|
||||
```graphql
|
||||
query {
|
||||
article {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Run the app:
|
||||
```bash
|
||||
yarn run build
|
||||
yarn run dev
|
||||
```
|
||||
- Test the app
|
||||
Visit [http://localhost:3000](http://localhost:3000) to view the app
|
||||
|
||||
## Serverless Mode
|
||||
|
||||
With Next.js 8, each page in the `pages` directory becomes a serverless lambda. To enable `serverless` mode, we add the `serverless` build `target` in `next.config.js`.
|
||||
|
||||
```
|
||||
module.exports = {
|
||||
target: "serverless",
|
||||
};
|
||||
```
|
||||
|
||||
That's it! Now build the serverless app by running the following command:
|
||||
|
||||
```
|
||||
yarn run build
|
||||
```
|
||||
|
||||
In the `.next` folder, you will see a `serverless` folder generated after the build. Inside that there is a `pages` folder, which will have outputs of lambda per page.
|
||||
|
||||
```
|
||||
pages/index.js => .next/serverless/pages/index.js
|
||||
pages/login.js => .next/serverless/pages/login.js
|
||||
pages/signup.js => .next/serverless/pages/signup.js
|
||||
pages/articles.js => .next/serverless/pages/articles.js
|
||||
```
|
||||
|
||||
## Deploy to now.sh
|
||||
|
||||
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)):
|
||||
|
||||
```bash
|
||||
npm install -g now
|
||||
now
|
||||
```
|
||||
*Note*: Older versions of now-cli doesn't support serverless mode.
|
||||
Once the deployment is successful, you will be able to navigate to pages `/` and `/login`, `/signup` with each one internally being a lambda function which `now` manages.
|
@ -0,0 +1,75 @@
|
||||
import { Query } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const articleQuery = gql`
|
||||
query {
|
||||
article {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
export default function ArticleList () {
|
||||
return (
|
||||
<Query query={articleQuery}>
|
||||
{({ loading, error, data: { article} }) => {
|
||||
if (error) return <ErrorMessage message='Error loading articles.' />
|
||||
if (loading) return <div>Loading</div>
|
||||
|
||||
return (
|
||||
<section>
|
||||
<ul>
|
||||
{article.map((a, index) => (
|
||||
<li key={a.id}>
|
||||
<div>
|
||||
<span>{index + 1}. </span>
|
||||
<a>{a.title}</a>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<style jsx>{`
|
||||
section {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
li {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
a {
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
text-decoration: none;
|
||||
padding-bottom: 0;
|
||||
border: 0;
|
||||
}
|
||||
span {
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
button:before {
|
||||
align-self: center;
|
||||
border-style: solid;
|
||||
border-width: 6px 4px 0 4px;
|
||||
border-color: #ffffff transparent transparent transparent;
|
||||
content: '';
|
||||
height: 0;
|
||||
margin-right: 5px;
|
||||
width: 0;
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
)
|
||||
}}
|
||||
</Query>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
import Link from 'next/link'
|
||||
import { logout } from '../utils/auth'
|
||||
|
||||
const Header = props => (
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href='/'>
|
||||
<a>Home</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href='/login'>
|
||||
<a>Login</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href='/signup'>
|
||||
<a>Signup</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href='/articles'>
|
||||
<a>Articles</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<button onClick={logout}>Logout</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<style jsx>{`
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 0.2rem;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
}
|
||||
`}</style>
|
||||
</header>
|
||||
)
|
||||
|
||||
export default Header
|
@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import Header from './header'
|
||||
|
||||
const Layout = props => (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>Authentication with Next.js using JWT and Hasura GraphQL Engine</title>
|
||||
</Head>
|
||||
<style jsx global>{`
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, Noto Sans, sans-serif, 'Apple Color Emoji',
|
||||
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 65rem;
|
||||
margin: 1.5rem auto;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
`}</style>
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<div className='container'>{props.children}</div>
|
||||
</main>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
export default Layout
|
@ -0,0 +1,56 @@
|
||||
import { ApolloClient, InMemoryCache, HttpLink } from 'apollo-boost'
|
||||
import { ApolloLink, concat } from 'apollo-link'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
import cookie from 'js-cookie'
|
||||
|
||||
let apolloClient = null
|
||||
|
||||
// Polyfill fetch() on the server (used by apollo-client)
|
||||
if (!process.browser) {
|
||||
global.fetch = fetch
|
||||
}
|
||||
const getToken = () => {
|
||||
let token = null;
|
||||
if (typeof document !== 'undefined') {
|
||||
token = 'Bearer ' + cookie.get('token')
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
function create (initialState) {
|
||||
const authMiddleware = new ApolloLink((operation, forward) => {
|
||||
// add the authorization to the headers
|
||||
operation.setContext({
|
||||
headers: {
|
||||
authorization: getToken(),
|
||||
}
|
||||
})
|
||||
|
||||
return forward(operation);
|
||||
})
|
||||
const httpLink = new HttpLink({
|
||||
uri: 'https://my-app.herokuapp.com/v1alpha1/graphql', // Server URL (must be absolute)
|
||||
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
|
||||
})
|
||||
return new ApolloClient({
|
||||
connectToDevTools: process.browser,
|
||||
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
|
||||
link: concat(authMiddleware, httpLink),
|
||||
cache: new InMemoryCache().restore(initialState || {})
|
||||
})
|
||||
}
|
||||
|
||||
export default function initApollo (initialState) {
|
||||
// Make sure to create a new client for every server-side request so that data
|
||||
// isn't shared between connections (which would be bad)
|
||||
if (!process.browser) {
|
||||
return create(initialState)
|
||||
}
|
||||
|
||||
// Reuse client on the client-side
|
||||
if (!apolloClient) {
|
||||
apolloClient = create(initialState)
|
||||
}
|
||||
|
||||
return apolloClient
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "nextjs-jwt-hasura-graphql",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"apollo-boost": "^0.3.1",
|
||||
"encoding": "^0.1.12",
|
||||
"graphql": "^14.2.1",
|
||||
"isomorphic-unfetch": "^3.0.0",
|
||||
"js-cookie": "^2.2.0",
|
||||
"next": "8.0.3",
|
||||
"next-cookies": "^1.0.4",
|
||||
"react": "^16.7.0",
|
||||
"react-apollo": "^2.5.3",
|
||||
"react-dom": "^16.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"http-proxy": "^1.17.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import Router from 'next/router'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
import nextCookie from 'next-cookies'
|
||||
import Layout from '../components/layout'
|
||||
import ArticleList from '../components/ArticleList'
|
||||
import { withAuthSync } from '../utils/auth'
|
||||
|
||||
const Articles = props => {
|
||||
return (
|
||||
<Layout>
|
||||
<ArticleList />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuthSync(Articles)
|
@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import Layout from '../components/layout'
|
||||
|
||||
const Home = props => (
|
||||
<Layout>
|
||||
<h1>Authentication example with JWT and Hasura GraphQL</h1>
|
||||
|
||||
<p>Steps to test the functionality:</p>
|
||||
|
||||
<ol>
|
||||
<li>Click signup and enter username and password.</li>
|
||||
<li>
|
||||
Click home and click articles again, notice how your session is being
|
||||
used through a token.
|
||||
</li>
|
||||
<li>
|
||||
Click logout and try to go to articles again. You'll get redirected to
|
||||
the `/login` route.
|
||||
</li>
|
||||
</ol>
|
||||
<style jsx>{`
|
||||
li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`}</style>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
export default Home
|
@ -0,0 +1,135 @@
|
||||
import { Component } from 'react'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
import Layout from '../components/layout'
|
||||
import { login } from '../utils/auth'
|
||||
|
||||
class Login extends Component {
|
||||
static getInitialProps ({ req }) {
|
||||
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http'
|
||||
|
||||
const apiUrl = process.browser
|
||||
? `${protocol}://${window.location.host}/login`
|
||||
: `${protocol}://${req.headers.host}/login`
|
||||
|
||||
return { apiUrl }
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = { username: '', 'password': '', error: '' }
|
||||
this.handleChangeUsername = this.handleChangeUsername.bind(this)
|
||||
this.handleChangePassword = this.handleChangePassword.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
}
|
||||
|
||||
handleChangeUsername (event) {
|
||||
this.setState({ username: event.target.value })
|
||||
}
|
||||
|
||||
handleChangePassword (event) {
|
||||
this.setState({ password: event.target.value })
|
||||
}
|
||||
|
||||
async handleSubmit (event) {
|
||||
event.preventDefault()
|
||||
this.setState({ error: '' })
|
||||
const username = this.state.username
|
||||
const password = this.state.password
|
||||
const url = this.props.apiUrl
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
})
|
||||
if (response.ok) {
|
||||
const { token } = await response.json()
|
||||
login({ token })
|
||||
} else {
|
||||
console.log('Login failed.')
|
||||
// https://github.com/developit/unfetch#caveats
|
||||
let error = new Error(response.statusText)
|
||||
error.response = response
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'You have an error in your code or there are Network issues.',
|
||||
error
|
||||
)
|
||||
this.setState({ error: error.message })
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Layout>
|
||||
<div className='login'>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<label htmlFor='username'>Enter username</label>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
id='username'
|
||||
name='username'
|
||||
value={this.state.username}
|
||||
onChange={this.handleChangeUsername}
|
||||
/>
|
||||
<input
|
||||
type='text'
|
||||
id='password'
|
||||
name='password'
|
||||
value={this.state.password}
|
||||
onChange={this.handleChangePassword}
|
||||
/>
|
||||
|
||||
<button type='submit'>Login</button>
|
||||
|
||||
<p className={`error ${this.state.error && 'show'}`}>
|
||||
{this.state.error && `Error: ${this.state.error}`}
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.login {
|
||||
max-width: 340px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 8px;
|
||||
margin: 0.3rem 0 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 0.5rem 0 0;
|
||||
display: none;
|
||||
color: brown;
|
||||
}
|
||||
|
||||
.error.show {
|
||||
display: block;
|
||||
}
|
||||
`}</style>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Login
|
@ -0,0 +1,150 @@
|
||||
import { Component } from 'react'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
import Layout from '../components/layout'
|
||||
import { signup } from '../utils/auth'
|
||||
|
||||
class Signup extends Component {
|
||||
static getInitialProps ({ req }) {
|
||||
const protocol = process.env.NODE_ENV === 'production' ? 'https' : 'http'
|
||||
|
||||
const apiUrl = process.browser
|
||||
? `${protocol}://${window.location.host}/signup`
|
||||
: `${protocol}://${req.headers.host}/signup`
|
||||
|
||||
return { apiUrl }
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = { username: '', password: '', confirmPassword: '', error: '' }
|
||||
this.handleChangeUsername = this.handleChangeUsername.bind(this)
|
||||
this.handleChangePassword = this.handleChangePassword.bind(this)
|
||||
this.handleChangeConfirmPassword = this.handleChangeConfirmPassword.bind(this)
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
}
|
||||
|
||||
handleChangeUsername (event) {
|
||||
this.setState({ username: event.target.value })
|
||||
}
|
||||
|
||||
handleChangePassword (event) {
|
||||
this.setState({ password: event.target.value })
|
||||
}
|
||||
|
||||
handleChangeConfirmPassword (event) {
|
||||
this.setState({ confirmPassword: event.target.value })
|
||||
}
|
||||
|
||||
async handleSubmit (event) {
|
||||
event.preventDefault()
|
||||
this.setState({ error: '' })
|
||||
const username = this.state.username
|
||||
const password = this.state.password
|
||||
const confirmPassword = this.state.confirmPassword
|
||||
const url = this.props.apiUrl
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password, confirmPassword })
|
||||
})
|
||||
if (response.ok) {
|
||||
const { token } = await response.json()
|
||||
signup({ token })
|
||||
} else {
|
||||
console.log('Login failed.')
|
||||
// https://github.com/developit/unfetch#caveats
|
||||
let error = new Error(response.statusText)
|
||||
error.response = response
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'You have an error in your code or there are Network issues.',
|
||||
error
|
||||
)
|
||||
this.setState({ error: error.message })
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Layout>
|
||||
<div className='login'>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<label htmlFor='username'>Enter username</label>
|
||||
|
||||
<input
|
||||
type='text'
|
||||
id='username'
|
||||
name='username'
|
||||
value={this.state.username}
|
||||
onChange={this.handleChangeUsername}
|
||||
/>
|
||||
<label htmlFor='username'>Enter Password</label>
|
||||
<input
|
||||
type='text'
|
||||
id='password'
|
||||
name='password'
|
||||
value={this.state.password}
|
||||
onChange={this.handleChangePassword}
|
||||
/>
|
||||
<label htmlFor='username'>Confirm Password</label>
|
||||
<input
|
||||
type='text'
|
||||
id='confirmPassword'
|
||||
name='confirmPassword'
|
||||
value={this.state.confirmPassword}
|
||||
onChange={this.handleChangeConfirmPassword}
|
||||
/>
|
||||
|
||||
<button type='submit'>Signup</button>
|
||||
|
||||
<p className={`error ${this.state.error && 'show'}`}>
|
||||
{this.state.error && `Error: ${this.state.error}`}
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.login {
|
||||
max-width: 340px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 8px;
|
||||
margin: 0.3rem 0 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 0.5rem 0 0;
|
||||
display: none;
|
||||
color: brown;
|
||||
}
|
||||
|
||||
.error.show {
|
||||
display: block;
|
||||
}
|
||||
`}</style>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Signup
|
@ -0,0 +1,59 @@
|
||||
const { createServer } = require('http')
|
||||
const httpProxy = require('http-proxy')
|
||||
const { parse } = require('url')
|
||||
const next = require('next')
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({ dev })
|
||||
const handle = app.getRequestHandler()
|
||||
|
||||
const proxy = httpProxy.createProxyServer()
|
||||
const target = 'http://localhost:8080'
|
||||
|
||||
app.prepare().then(() => {
|
||||
createServer((req, res) => {
|
||||
const parsedUrl = parse(req.url, true)
|
||||
const { pathname, query } = parsedUrl
|
||||
|
||||
switch (pathname) {
|
||||
case '/':
|
||||
app.render(req, res, '/', query)
|
||||
break
|
||||
|
||||
case '/sign-in':
|
||||
app.render(req, res, '/login', query)
|
||||
break
|
||||
|
||||
case '/sign-up':
|
||||
app.render(req, res, '/signup', query)
|
||||
break
|
||||
|
||||
case '/login':
|
||||
proxy.web(req, res, { target }, error => {
|
||||
console.log('Error!', error)
|
||||
})
|
||||
break
|
||||
|
||||
case '/signup':
|
||||
proxy.web(req, res, { target }, error => {
|
||||
console.log('Error!', error)
|
||||
})
|
||||
break
|
||||
|
||||
case '/articles':
|
||||
app.render(req, res, '/articles', query)
|
||||
break
|
||||
|
||||
case '/api/profile.js':
|
||||
proxy.web(req, res, { target }, error => console.log('Error!', error))
|
||||
break
|
||||
|
||||
default:
|
||||
handle(req, res, parsedUrl)
|
||||
break
|
||||
}
|
||||
}).listen(3000, err => {
|
||||
if (err) throw err
|
||||
console.log('> Ready on http://localhost:3000')
|
||||
})
|
||||
})
|
@ -0,0 +1,91 @@
|
||||
import { Component } from 'react'
|
||||
import Router from 'next/router'
|
||||
import nextCookie from 'next-cookies'
|
||||
import cookie from 'js-cookie'
|
||||
|
||||
export const login = async ({ token }) => {
|
||||
cookie.set('token', token, { expires: 1 })
|
||||
// localStorage.setItem('token', token)
|
||||
Router.push('/articles')
|
||||
}
|
||||
|
||||
export const signup = async ({ token }) => {
|
||||
cookie.set('token', token, { expires: 1 })
|
||||
// localStorage.setItem('token', token)
|
||||
Router.push('/')
|
||||
}
|
||||
|
||||
export const logout = () => {
|
||||
cookie.remove('token')
|
||||
// to support logging out from all windows
|
||||
window.localStorage.setItem('logout', Date.now())
|
||||
// window.localStorage.removeItem('token')
|
||||
Router.push('/login')
|
||||
}
|
||||
|
||||
// Gets the display name of a JSX component for dev tools
|
||||
const getDisplayName = Component =>
|
||||
Component.displayName || Component.name || 'Component'
|
||||
|
||||
export const withAuthSync = WrappedComponent =>
|
||||
class extends Component {
|
||||
static displayName = `withAuthSync(${getDisplayName(WrappedComponent)})`
|
||||
|
||||
static async getInitialProps (ctx) {
|
||||
const token = auth(ctx)
|
||||
|
||||
const componentProps =
|
||||
WrappedComponent.getInitialProps &&
|
||||
(await WrappedComponent.getInitialProps(ctx))
|
||||
|
||||
return { ...componentProps, token }
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.syncLogout = this.syncLogout.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('storage', this.syncLogout)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('storage', this.syncLogout)
|
||||
window.localStorage.removeItem('logout')
|
||||
}
|
||||
|
||||
syncLogout (event) {
|
||||
if (event.key === 'logout') {
|
||||
console.log('logged out from storage!')
|
||||
Router.push('/login')
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return <WrappedComponent {...this.props} />
|
||||
}
|
||||
}
|
||||
|
||||
export const auth = ctx => {
|
||||
const { token } = nextCookie(ctx)
|
||||
|
||||
/*
|
||||
* This happens on server only, ctx.req is available means it's being
|
||||
* rendered on server. If we are on server and token is not available,
|
||||
* means user is not logged in.
|
||||
*/
|
||||
if (ctx.req && !token) {
|
||||
ctx.res.writeHead(302, { Location: '/login' })
|
||||
ctx.res.end()
|
||||
return
|
||||
}
|
||||
|
||||
// We already checked for server. This should only happen on client.
|
||||
if (!token) {
|
||||
Router.push('/login')
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
endpoint: http://localhost:8080
|
@ -0,0 +1,342 @@
|
||||
--
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
-- Dumped from database version 11.2 (Debian 11.2-1.pgdg90+1)
|
||||
-- Dumped by pg_dump version 11.2
|
||||
|
||||
-- Started on 2019-04-03 15:23:09 IST
|
||||
|
||||
--
|
||||
-- TOC entry 4 (class 2615 OID 2200)
|
||||
-- Name: public; Type: SCHEMA; Schema: -; Owner: -
|
||||
--
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- TOC entry 220 (class 1259 OID 16612)
|
||||
-- Name: user; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public."user" (
|
||||
id uuid DEFAULT public.gen_random_uuid() NOT NULL,
|
||||
username character varying(255) NOT NULL,
|
||||
password character varying(255) NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
active boolean DEFAULT true
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 224 (class 1259 OID 16669)
|
||||
-- Name: article; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.article (
|
||||
id integer NOT NULL,
|
||||
title text NOT NULL,
|
||||
content text NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
user_id uuid NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 223 (class 1259 OID 16667)
|
||||
-- Name: articles_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.articles_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3042 (class 0 OID 0)
|
||||
-- Dependencies: 223
|
||||
-- Name: articles_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.articles_id_seq OWNED BY public.article.id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 217 (class 1259 OID 16598)
|
||||
-- Name: knex_migrations; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.knex_migrations (
|
||||
id integer NOT NULL,
|
||||
name character varying(255),
|
||||
batch integer,
|
||||
migration_time timestamp with time zone
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 216 (class 1259 OID 16596)
|
||||
-- Name: knex_migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.knex_migrations_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3043 (class 0 OID 0)
|
||||
-- Dependencies: 216
|
||||
-- Name: knex_migrations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.knex_migrations_id_seq OWNED BY public.knex_migrations.id;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 219 (class 1259 OID 16606)
|
||||
-- Name: knex_migrations_lock; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.knex_migrations_lock (
|
||||
index integer NOT NULL,
|
||||
is_locked integer
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 218 (class 1259 OID 16604)
|
||||
-- Name: knex_migrations_lock_index_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE public.knex_migrations_lock_index_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3044 (class 0 OID 0)
|
||||
-- Dependencies: 218
|
||||
-- Name: knex_migrations_lock_index_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE public.knex_migrations_lock_index_seq OWNED BY public.knex_migrations_lock.index;
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 221 (class 1259 OID 16628)
|
||||
-- Name: role; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.role (
|
||||
id uuid DEFAULT public.gen_random_uuid() NOT NULL,
|
||||
name character varying(255) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 222 (class 1259 OID 16638)
|
||||
-- Name: user_role; Type: TABLE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TABLE public.user_role (
|
||||
id uuid DEFAULT public.gen_random_uuid() NOT NULL,
|
||||
role_id uuid,
|
||||
user_id uuid
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2878 (class 2604 OID 16672)
|
||||
-- Name: article id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.article ALTER COLUMN id SET DEFAULT nextval('public.articles_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2871 (class 2604 OID 16601)
|
||||
-- Name: knex_migrations id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.knex_migrations ALTER COLUMN id SET DEFAULT nextval('public.knex_migrations_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2872 (class 2604 OID 16609)
|
||||
-- Name: knex_migrations_lock index; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.knex_migrations_lock ALTER COLUMN index SET DEFAULT nextval('public.knex_migrations_lock_index_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2904 (class 2606 OID 16678)
|
||||
-- Name: article articles_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.article
|
||||
ADD CONSTRAINT articles_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2883 (class 2606 OID 16611)
|
||||
-- Name: knex_migrations_lock knex_migrations_lock_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.knex_migrations_lock
|
||||
ADD CONSTRAINT knex_migrations_lock_pkey PRIMARY KEY (index);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2881 (class 2606 OID 16603)
|
||||
-- Name: knex_migrations knex_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.knex_migrations
|
||||
ADD CONSTRAINT knex_migrations_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2892 (class 2606 OID 16635)
|
||||
-- Name: role role_id_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.role
|
||||
ADD CONSTRAINT role_id_unique UNIQUE (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2894 (class 2606 OID 16637)
|
||||
-- Name: role role_name_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.role
|
||||
ADD CONSTRAINT role_name_unique UNIQUE (name);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2896 (class 2606 OID 16633)
|
||||
-- Name: role role_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.role
|
||||
ADD CONSTRAINT role_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2886 (class 2606 OID 16624)
|
||||
-- Name: user user_id_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."user"
|
||||
ADD CONSTRAINT user_id_unique UNIQUE (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2888 (class 2606 OID 16622)
|
||||
-- Name: user user_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."user"
|
||||
ADD CONSTRAINT user_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2898 (class 2606 OID 16645)
|
||||
-- Name: user_role user_role_id_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_role
|
||||
ADD CONSTRAINT user_role_id_unique UNIQUE (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2900 (class 2606 OID 16643)
|
||||
-- Name: user_role user_role_pkey; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_role
|
||||
ADD CONSTRAINT user_role_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2890 (class 2606 OID 16626)
|
||||
-- Name: user user_username_unique; Type: CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public."user"
|
||||
ADD CONSTRAINT user_username_unique UNIQUE (username);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2884 (class 1259 OID 16627)
|
||||
-- Name: user_active_index; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX user_active_index ON public."user" USING btree (active);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2901 (class 1259 OID 16646)
|
||||
-- Name: user_role_role_id_index; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX user_role_role_id_index ON public.user_role USING btree (role_id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2902 (class 1259 OID 16652)
|
||||
-- Name: user_role_user_id_index; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX user_role_user_id_index ON public.user_role USING btree (user_id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2907 (class 2606 OID 16731)
|
||||
-- Name: article articles_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.article
|
||||
ADD CONSTRAINT articles_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2905 (class 2606 OID 16647)
|
||||
-- Name: user_role user_role_role_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_role
|
||||
ADD CONSTRAINT user_role_role_id_foreign FOREIGN KEY (role_id) REFERENCES public.role(id);
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 2906 (class 2606 OID 16653)
|
||||
-- Name: user_role user_role_user_id_foreign; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.user_role
|
||||
ADD CONSTRAINT user_role_user_id_foreign FOREIGN KEY (user_id) REFERENCES public."user"(id);
|
||||
|
||||
|
||||
-- Completed on 2019-04-03 15:23:10 IST
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
@ -0,0 +1,88 @@
|
||||
functions: []
|
||||
query_templates: []
|
||||
remote_schemas: []
|
||||
tables:
|
||||
- array_relationships: []
|
||||
delete_permissions: []
|
||||
event_triggers: []
|
||||
insert_permissions: []
|
||||
object_relationships: []
|
||||
select_permissions:
|
||||
- comment: null
|
||||
permission:
|
||||
allow_aggregations: true
|
||||
columns:
|
||||
- id
|
||||
- title
|
||||
- content
|
||||
- created_at
|
||||
- user_id
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table: article
|
||||
update_permissions: []
|
||||
- array_relationships: []
|
||||
delete_permissions: []
|
||||
event_triggers: []
|
||||
insert_permissions: []
|
||||
object_relationships: []
|
||||
select_permissions: []
|
||||
table: role
|
||||
update_permissions: []
|
||||
- array_relationships: []
|
||||
delete_permissions: []
|
||||
event_triggers: []
|
||||
insert_permissions:
|
||||
- comment: null
|
||||
permission:
|
||||
check:
|
||||
id:
|
||||
_eq: X-Hasura-User-Id
|
||||
columns:
|
||||
- active
|
||||
- created_at
|
||||
- id
|
||||
- password
|
||||
- username
|
||||
set: {}
|
||||
role: user
|
||||
object_relationships: []
|
||||
select_permissions:
|
||||
- comment: null
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- id
|
||||
- username
|
||||
- password
|
||||
- created_at
|
||||
- active
|
||||
filter:
|
||||
id:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table: user
|
||||
update_permissions:
|
||||
- comment: null
|
||||
permission:
|
||||
columns:
|
||||
- active
|
||||
- created_at
|
||||
- id
|
||||
- password
|
||||
- username
|
||||
filter:
|
||||
id:
|
||||
_eq: X-Hasura-User-Id
|
||||
set: {}
|
||||
role: user
|
||||
- array_relationships: []
|
||||
delete_permissions: []
|
||||
event_triggers: []
|
||||
insert_permissions: []
|
||||
object_relationships: []
|
||||
select_permissions: []
|
||||
table: user_role
|
||||
update_permissions: []
|
83
community/sample-apps/nextjs-8-serverless/with-apollo/.gitignore
vendored
Normal file
83
community/sample-apps/nextjs-8-serverless/with-apollo/.gitignore
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
@ -30,7 +30,7 @@
|
||||
- Clone this repo:
|
||||
```bash
|
||||
git clone https://github.com/hasura/graphql-engine
|
||||
cd graphql-engine/community/sample-apps/nextjs-8-serverless
|
||||
cd graphql-engine/community/sample-apps/nextjs-8-serverless/with-apollo
|
||||
```
|
||||
|
||||
- Install npm modules:
|
@ -0,0 +1,61 @@
|
||||
import React from 'react'
|
||||
import initApollo from './init-apollo'
|
||||
import Head from 'next/head'
|
||||
import { getDataFromTree } from 'react-apollo'
|
||||
|
||||
export default App => {
|
||||
return class Apollo extends React.Component {
|
||||
static displayName = 'withApollo(App)'
|
||||
static async getInitialProps (ctx) {
|
||||
const { Component, router } = ctx
|
||||
|
||||
let appProps = {}
|
||||
if (App.getInitialProps) {
|
||||
appProps = await App.getInitialProps(ctx)
|
||||
}
|
||||
|
||||
// Run all GraphQL queries in the component tree
|
||||
// and extract the resulting data
|
||||
const apollo = initApollo()
|
||||
if (!process.browser) {
|
||||
try {
|
||||
// Run all GraphQL queries
|
||||
await getDataFromTree(
|
||||
<App
|
||||
{...appProps}
|
||||
Component={Component}
|
||||
router={router}
|
||||
apolloClient={apollo}
|
||||
/>
|
||||
)
|
||||
} catch (error) {
|
||||
// Prevent Apollo Client GraphQL errors from crashing SSR.
|
||||
// Handle them in components via the data.error prop:
|
||||
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
|
||||
console.error('Error while running `getDataFromTree`', error)
|
||||
}
|
||||
|
||||
// getDataFromTree does not call componentWillUnmount
|
||||
// head side effect therefore need to be cleared manually
|
||||
Head.rewind()
|
||||
}
|
||||
|
||||
// Extract query data from the Apollo store
|
||||
const apolloState = apollo.cache.extract()
|
||||
|
||||
return {
|
||||
...appProps,
|
||||
apolloState
|
||||
}
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.apolloClient = initApollo(props.apolloState)
|
||||
}
|
||||
|
||||
render () {
|
||||
return <App {...this.props} apolloClient={this.apolloClient} />
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
target: "serverless"
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [
|
||||
{ "src": "next.config.js", "use": "@now/next" }
|
||||
]
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import App, { Container } from 'next/app'
|
||||
import React from 'react'
|
||||
import withApolloClient from '../lib/with-apollo-client'
|
||||
import { ApolloProvider } from 'react-apollo'
|
||||
|
||||
class MyApp extends App {
|
||||
render () {
|
||||
const { Component, pageProps, apolloClient } = this.props
|
||||
return (
|
||||
<Container>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<Component {...pageProps} />
|
||||
</ApolloProvider>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withApolloClient(MyApp)
|
Loading…
Reference in New Issue
Block a user