In this section, you learn how to set up your fullstack serverless application with Fauna.
To create a new fullstack serverless app, run the following command in your terminal.
$ npm init next-app fauna-shop --use-npm
Run your app to ensure everything is working correctly before making any changes.
$ cd fauna-shop
$ npm run dev
Navigate to http://localhost:3000 in your browser and review the running application.
Run the following command to add the [Apollo GraphQL client][apollo-client] and GraphQL dependencies to your application.
$ npm install @apollo/client graphql
Create a new file called apollo-client.js
in the project’s root with the following code.
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
const httpLink = createHttpLink({
// Uncomment the appropriate line according to the
// region group where you created your database.
uri: 'https://graphql.fauna.com/graphql',
// uri: 'https://graphql.eu.fauna.com/graphql',
// uri: 'https://graphql.us.fauna.com/graphql',
});
const authLink = setContext((_, { headers }) => {
const token = process.env.NEXT_PUBLIC_FAUNA_SECRET
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
export default client;
Region groups give you control over where your data resides. You choose the Region Group for your application when you create your database in the first chapter. To learn more about Region Groups visit the documentation.
In the previous code snippet, there is an environment variable called NEXT_PUBLIC_FAUNA_SECRET. You ship your client application with a Fauna secret that allows limited access to Fauna resources. A suggested practice is to limit this key to registering and authenticating users. Authenticated users then receive temporary access tokens when they login that grant access to additional resources.
First, create a role that only has permissions to call the RegisterUser and LoginUser functions you created in the User authentication section of the first chapter.
Navigate to the Security section of the Fauna dashboard and choose Roles then New Custom Role.
FrontEndRole
.When you invoke a UDF that does not have its own role, the UDF runs with the same permissions as the identity that invoked it. In the first section, you invoke your UDFs from the Shell and GraphQL sections of the dashboard. When you are in the dashboard, commands you invoke are run with admin permissions by default.
The key that you create for your front-end application only has permission to invoke the RegisterUser and LoginUser UDFs, but the UDFs need permission to create and read documents in the Owner collection. You do not want to give these permissions directly to the front-end role. Instead, create tightly-scoped roles for each UDF.
Return to the Security section of the dashboard and create another new role.
RegisterUserUDF
.Navigate to the Functions section, select the RegisterUser UDF, and update the role to use the new RegisterUserUDF role.
Navigate to the Shell and test your UDF by using the Run As feature to invoke the UDF as FrontEndRole.
To use the Run As feature, copy the following query, paste it into the web shell, select FrontEndRole from the Run As menu, and choose Run Query As to invoke your UDF.
Call(
"RegisterUser",
// ["email", "password", "name"]
["security@fauna-labs.com", "qZXUEhaNdng9", "Improved Security"]
)
{
ref: Ref(Collection("Owner"), "317182757128110665"),
ts: 1638747899080000,
data: {
email: "security@fauna-labs.com",
name: "Improved Security"
}
}
Create another new role named LoginUserUDF
. The LoginUser UDF needs permission to read from the findOwnerByEmail index to locate the correct user and to read from the Owner collection to compare the hashed credentials for that user.
Navigate to the Functions section and update the role of your LoginUser UDF to use the new LoginUserUDF role. Return to the Shell and test the UDF, again using Run As to invoke the UDF as the FrontEnd role.
Call(
"LoginUser",
// ["email", "password"]
["security@fauna-labs.com", "qZXUEhaNdng9"]
)
{
secret: "<token>",
ttl: Time("2021-12-06T00:56:46.768682Z"),
email: "security@fauna-labs.com"
}
Now that your roles and UDFs are working correctly, create a front-end key to store in your application. Navigate to the Security section and choose New Key.
Copy the secret key to use in the next step.
The secret key cannot be displayed once you navigate away from this page! If you lose a key, you can create a new key with the same role and revoke the old key.
Create a .env.local
file in the root of your application and add this secret key as an environment variable.
NEXT_PUBLIC_FAUNA_SECRET=fnxxxxxxxxxxxxx
Restart your Next.js application after updating the environment variable.
Adding an ApolloProvider for Fauna allows you to execute GraphQL queries and mutations from your components. To add an ApolloProvider, replace the contents of pages/_app.js with the following code.
import { ApolloProvider } from '@apollo/client';
import client from '../apollo-client';
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
return (
<>
<ApolloProvider client={client}>
<Component {...pageProps} />
</ApolloProvider>
</>
);
}
export default MyApp;
Replace the contents of pages/index.js
with the following code. Make sure to use a valid username and password for a registered user. If you haven’t registered any users yet refer back to the Authentication section for instructions on signing up a new user.
import styles from '../styles/Home.module.css'
import { useMutation, gql } from "@apollo/client";
const LOGIN = gql`
mutation OwnerLogin($email: String!, $password: String! ) {
login(email: $email, password: $password) {
ttl
secret
email
}
}
`;
export default function Home() {
const [loginFunc, { data, loading, error }] = useMutation(LOGIN)
if (loading) {
return <div>Loading...</div>;
}
if (error) {
console.error(error);
return null;
}
const doLogin = e => {
e.preventDefault();
loginFunc({
variables: {
email: 'security@fauna-labs.com',
password: 'qZXUEhaNdng9',
}
})
.then(resp => console.log('==>', resp))
.catch(e => console.log(e))
}
return (
<div className={styles.container}>
<button onClick={doLogin}>Login</button>
</div>
)
}
Run your application with the following command.
$ npm run dev
Navigate to localhost:3000 and open your browser’s developer tools to the Console tab. Choose the Login button. You should see the response from the login mutation in the console!
In this session, you configured Fauna to perform user authentication with least privileged access and connected your front-end application to Fauna.
In the next section, you implement client-side user registration and login forms for your application.
📙 Get the final code for this section here