This article shows how to create a full-fledged web application with a database, GraphQL, and a reactive frontend.
Introduction
Simultaneously developing both server and client sides of web applications can be a challenge. Developers need to understand many low-level concepts, and this can prove an obstacle when solving some problems.
However, in recent years, the tech community has come up with helpful tools that encapsulate advanced approaches and architecture solutions. By utilizing them, you can easily organize data storage, use ready-made abstractions to build the logic of web applications, and much more.
In my two-article series, I will show you how to easily create a single-page application using Sapper, Prisma, and GraphQL Yoga.
We will use Prisma and GraphQL Yoga to develop the server side, and Sapper to develop the client application.
In the first part, we will consider the technology stack and configure the environment for application development.
Technology Stack
Svelte
Svelte is a framework that makes applications truly reactive and reduces their load volume. One of Svelte’s most beneficial properties is that it does not perform additional calculations for an application's virtual model.
Sapper
Sapper is a framework that allows you to write applications of different sizes using Svelte. Sapper internally launches a server that distributes a Svelte application with optimizations and SEO-friendly server-side rendering.
In this article, we will not consider Svelte separately from Sapper.
Prisma
To create the backend and work with data, we will use Prisma and GraphQL Yoga.
Prisma is a set of utilities for design and working with relational databases. We will work with Prisma Server and Prisma CLI.
Prisma Server is a standalone server that displays graphql endpoint/graphql playground outside, and connects graphql queries to the database and correctly translates them to the database inside. Also, Prisma Server is used for database schema management and migration.
GraphQL Yoga
Since Prisma provides root access to the database for each client, yoga-server will be used on the surface of the application. GraphQL-yoga fully supports the GraphQL specification and comes with a convenient playground in which we will test our queries.
Infrastructure Deployment
This setup way assumes that `Docker` and `docker-compose` are already pre-installed in the system.
Launching Sapper
Use the following snippet from the official documentation to deploy the Sapper application locally:
```bash
npx degit "sveltejs/sapper-template#rollup" svelte-graphql-quiz
cd svelte-graphql-quiz
npm install
npm run dev
```
This command will create a Sapper project in the `svelte-graphql-quiz` directory, install the dependencies, and start the server in development mode.
Launching Prisma
The environment deployment process for Prisma requires more configurations as it affects more infrastructure components.
-
prisma-cli
The first step is to install prisma-cli
. This is a command-line utility that allows you to manage the entire Prisma infrastructure.
Using this utility, you can:
- Deploy the data schema to prisma-server (and to the database, respectively)
- Automatically generate a client for working with prisma-server, which we will use on a public server
Follow the link to explore the features of the utility in more detail.
```bash
yarn global add prisma
```
-
Prisma Configuration
Next, we need to create a small configuration for `prisma-cli
`. This configuration will provide the path to the data schema file (`datamodel.prisma`), the address of prisma-server (`endpoint
`), the description of files that need to be generated, as well as show where they should be placed (`generate
`).
```yml
endpoint: http://localhost:4466
datamodel: datamodel.prisma
generate:
- generator: javascript-client
output: ./prisma
- generator: graphql-schema
output: ./prisma/schema.graphql
```
-
docker-compose for prisma-server
Prisma-server comes in the form of a docker image, which we will use in describing the server configuration. Also, next to the prisma-server configuration, we need to describe the database configuration, since prisma-server
is directly connected to the database.
We will use `docker-compose
` to describe the configuration.
In this snippet, we described all the configurations necessary to run the Prisma server and the Postgres database using Docker.
```yml
# docker-compose.yml
version: "3"
services:
prisma:
image: prismagraphql/prisma:1.14
restart: always
ports:
- "4466:4466"
environment:
PRISMA_CONFIG: |
port: 4466
databases:
default:
connector: postgres
host: postgres
port: 5432
user: prisma
password: prisma
migrations: true
postgres:
image: postgres
restart: always
environment:
POSTGRES_USER: prisma
POSTGRES_PASSWORD: prisma
volumes:
- postgres:/var/lib/postgresql/data
volumes:
postgres:
```
The next step is to launch containers:
```bash
docker-compose up
# docker-compose up -d # for launch in the background move
```
Once the database is initialized and the server is started, yoga graphql playground, which also comes with the Prisma server, is available at the address `http://localhost: 4466`.
But the schema is not available at this stage and we can’t send any GraphQL queries, since the prisma-server and the database are empty. They know nothing about either the data schema or the data itself.
-
Data Schema
Once part of the infrastructure is ready, we can begin to describe the data schema using a syntax similar to GraphQL.
This way, we explicitly indicate entities on which CRUD operations can be performed through the GraphQL server. The database schema is automatically configured from this model.
```graphql
# datamodel.prisma
type QuizVariant {
label: String!
}
type QuizResults {
variant: QuizVariant! @relation(name: "Variant")
count: Int!
}
type Quiz {
participantCount: Int!
variants: [QuizVariant!]! @relation(link: TABLE, name: "QuizVariants")
results: [QuizResults!]! @relation(link: TABLE, name: "QuizResults")
}
```
In this schema, I created three entities (`Quiz
`, `QuizResults
`, `QuizVariant
`);
QuizVariant
- An entity describing a variant from a quiz.
- Contains one `
label
` field with the `String!
` type.
QuizResults
- Contains quiz results tied to a specific answer variant.
- The `
variant
` field - a reference to the `QuizVariant
` entity. - The `
count
` field with the `Int!
` type contains the number of results for the answer variant.
Quiz
- The main entity in the application.
- Stores answer variants in the `
variants
` field. - Also contains results for each variant in the `
results
` field.
You can find out more about the data schema syntax from the documentation.
-
Data Schema Deploy
We will deploy the data schema using the small and simple `prisma-cli
` command:
```bash
prisma deploy
```
Once the command is successfully executed, at the address `http://localhost: 4466`, we see that the GraphQL schema is available and we can use the CRUD operations `createQuiz
`, `updateQuiz
`, and others for each entity.
-
Client Generation
The last step in setting up the Prisma infrastructure is to generate a client to work with prisma-server.
```bash
prisma generate
```
In the prisma configuration, we indicated that we need `javascript-client
` and `graphql-schema
`.
After the successful completion of the `generate
` command, we see generated files in the `./prisma` directory.
-
Security
At this stage, prisma is up and running.
But one more important detail should not go unattended - security.
As mentioned before, `prisma
` has root access to the database and does not come with ACL systems. Therefore, the prisma-server
, like the database in the correct configuration, must be placed in the private network.
Also, it is advisable to configure access to Prisma via a token. You can find out more about this from the documentation.
GraphQL Yoga
To build the last part of the environment, we have to set up a GraphQL Yoga server.
For this, we will perform a little trick: create an instance of the GraphQL server. An important condition is that we initially launch a single web server for the GraphQL API and Sapper. Therefore, we describe the GraphQL Yoga server in the server Sapper file.
```js
// src/server.js
import { GraphQLServer } from "graphql-yoga";
import { prisma } from "../prisma/index";
const server = new GraphQLServer({
typeDefs: "./prisma/schema.graphql",
resolvers: {},
context: req => ({
...req,
prisma
})
});
```
When creating the server, we specified in the `typeDefs
` field the schema that the `prisma generate
` command generated for us. This schema is available on the GraphQL public server.
We also imported the client file `prisma/index` into the context of the resolver so that the client for prisma-server
is available in each resolver.
GraphQLServer + Sapper
Sapper comes as middleware, while yoga comes as a standalone server with its own routing settings and an `express
` instance inside, which in turn has its own request routing configuration.
We need a single web server. Therefore, we will create our own. Below is a server snippet with comments:
```js
// assign an empty `express` instance to the GraphQL server
server.express = express();
// create http server instance
server.createHttpServer({
endpoint: "/graphql",
playground: "/playground"
})
// use general middleware for both GraphQL endpoint/playground and Sapper
server.express.use(compression({ threshold: 0 }), sirv("static", { dev }));
// create customized query routing based on GraphQL http server settings
server.express.use("/", (req, res, next) => {
if (/^\/(playground|graphql).*/.test(req.url)) {
return;
}
// use Sapper for other queries
return sapper.middleware()(req, res, next);
});
// launch a server
server.start(PORT, () => {
console.log("server started");
});
```
Internal GraphQLServer
uses a pre-configured Express server. Having reassigned an empty instance to it, we were able to do our own routing and sort out requests on Sapper (which processes all requests in general) and requests on GraphQL, or open a yoga playground.
Now if we run the command to start the Sapper server in development mode: `yarn dev
`, then the GraphQL Yoga server will be available at the addresses `https://localhost:3000/playground` and graphql Endpoint will available at `https://localhost:3000/graphql`, and we will see the Sapper interface on the root path `https://localhost:3000`.
Resolvers for GraphQL Server
Since prisma-server will not be accessible on the web, we need to create our own handlers for GraphQL queries from the Sapper application.
Let's create a handler for `quizzes`, which will return a list of all quizzes by default.
```js
// src/resolvers/quizzes.jsx
export const Query = (_parent, _args, ctx) => {
return ctx.prisma.quizzes();
}
```
Also, let's create a file in which we will describe the `Query
` and `Mutation
` structure of resolvers.
```js
// src/resolvers/index.js
import { Query as QuizzesQuery } from "./quizzes";
export default {
Query: {
quizzes: QuizzesQueery
},
Mutation: {}
};
```
Next, update the GraphQL server constructor by adding our resolver
object.
```js
// src/server.js
...
import resolvers from "./resolvers"
...
const server = new GraphQLServer({
...
resolvers: resolvers
...
})
```
Now we can try to get the list of quizzes from the database through prisma-server
which we access through the GraphQL Yoga server.
The example of a query that can be done in yoga playground `http://localhost:3000/playground`.
```graphql
{
quizzes {
participantCount
}
}
```
We should receive the following response to our query:
```json
{
"quizzes": []
}
```
Apollo Client for Requests From Sapper
We built the infrastructure for working with data, set up the GraphQL server, and launched it together with Sapper. But the Sapper application still does not know anything about the GraphQL server.
Let's fix this!
Install apollo-client
to work with the GraphQL
server.
```bash
yarn add apollo-boost @apollo/react-hooks graphql svelte-apollo
```
Create an instance of the Apollo client:
```js
// src/client/apollo.js
import ApolliClient from 'apollo-boost';
const client = new ApolliClient({
// specify the address of the graphql yoga server
uri: 'http://localhost:3000/graphql'
});
export default client;
```
Apollo is ready to use! Let's test it.
Requesting Data From Sapper Via GraphQL
Now we are ready to send data from Sapper to GraphQL
API. Let's try to write the first simple request.
We will use Sapper’s `preload
` hook for data preloading and then present the result in the context of the `index
` route.
```html
<!-- src/routes/index.svelte -->
<script context="module">
import { gql } from "apollo-boost";
import client from "../client/apollo";
const QUIZES_QUERY = gql`
{
quizzes {
participantCount
}
}
`;
export async function preload() {
const {
data: { quizzes }
} = await client.query({ query: QUIZES_QUERY });
return {
quizzes
};
}
</script>
<script>
export let quizzes;
</script>
<h3>Quizzes in service: {quizzes.length}</h3>
```
Now if we go to https://localhost:3000, we will see the following response from the server:
```html
<h3>Quizzes in service: 0</h3>
```
Infrastructure deployment was successfully completed.
Conclusion
This article shows how to create a full-fledged web application with a database, GraphQL, and a reactive frontend.
Using Prisma, we designed a data schema for GraphQL server and database, migrated this schema to prisma-server and database.
Having created a public GraphQL server for processing requests from the Sapper application, we wrote a simple handler that requests data via prisma-client.
We made a request to GraphQL yoga in Sapper's `index` route and showed the result.
Prisma is a tool for quick prototyping of data and APIs for this data.
Sapper offers a zero-configuration approach to the quick development of reactive web applications. By combining these two tools, we got a powerful stack for prompt development.
The article was written in co-authorship with Roman Senin, a former Software Developer at Clever Solution Inc and Opporty.
In the second article, we will describe the interface and business logic of our application.
History
- 16th January, 2020: Initial version