### Model the data with GraphQL transform Add a [GraphQL API](https://docs.aws.amazon.com/appsync/latest/devguide/designing-a-graphql-api.html) to your app and automatically provision a database by running the following command from the root of your application directory: ```bash amplify add api ``` Accept the **default values** which are highlighted below: ```console ? Select from one of the below mentioned services: # GraphQL ? Here is the GraphQL API that we will create. Select a setting to edit or continue # Continue ? Choose a schema template: # Single object with fields (e.g., “Todo†with ID, name, description) ✔ Do you want to edit the schema now? (Y/n) # yes ``` The generated schema is for a Todo app. Replace the GraphQL schema at `amplify/backend/api/RestaurantAPI/schema.graphql` with the following: ```graphql type Restaurant @model @auth(rules: [{ allow: public }]){ id: ID! name: String! description: String! city: String! } ``` The GraphQL Transform Library provides custom directives you can use in your schema that allow you to do things like define data models, set up authentication and authorization rules, configure serverless functions as resolvers, and more. In the example schema above, the `@model` directive is part of Amplify's [GraphQL transformer](/cli/graphql/data-modeling) functionality and the `@auth` directive determines what [authorization rules](/cli/graphql/authorization-rules/) to apply. A type decorated with the `@model` directive will scaffold out the database table for the type (Restaurant table), the schema for CRUD (create, read, update, delete) and list operations, and the GraphQL resolvers needed to make everything work together. From the command line, press **enter** to accept the schema and continue to the next steps. ## Creating the API with database Create required backend resources for your configured api with the following command: ```bash amplify push ? Are you sure you want to continue? Y ? Do you want to update code for your updated GraphQL API Yes ? Do you want to generate GraphQL statements (queries, mutations and subscription) based on your schema types? This will overwrite your current graphql queries, mutations and subscriptions: Yes ``` ### Code generation Once the deployment is complete, the CLI will create a new directory in `src/graphql` with all of the GraphQL operations you will need for your API. The CLI also created an `API.service.ts` file in the `app` directory that you will be using shortly. Next, run the following command to check Amplify's status: ```bash amplify status ``` This will give us the current status of the Amplify project, including the current environment, any categories that have been created, and what state those categories are in. It should look similar to this: ```console Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | ------------- | --------- | ----------------- | | Api | RestaurantAPI | No Change | awscloudformation | ``` ### Testing your API You can open the AWS console to run Queries, Mutation, or Subscription against your new API at any time directly by running the following command: ```bash amplify console api ``` When prompted, select **GraphQL**. This will open the AWS AppSync console for you to run Queries, Mutations, or Subscriptions at the server and see the changes in your client app. ## Connect frontend to API Open `src/main.ts` and add the following code to configure the Angular project with Amplify: ```typescript import { Amplify } from 'aws-amplify'; import aws_exports from './aws-exports'; Amplify.configure(aws_exports); ``` <Callout warning={true}> On Angular 12+, Typescript [strict mode](https://www.typescriptlang.org/tsconfig#strict) is enabled by default, which will introduce some type errors when you run the application. Please see the TypeScript strict mode [troubleshooting guide](/lib/troubleshooting/strict-mode/q/platform/js/) to resolve them. </Callout> Update `tsconfig.app.json` to include the "node" compiler option in _types_: ```json "compilerOptions": { "types" : ["node"] } ``` <Callout> Depending on your TypeScript version you may need to rename `aws-exports.js` to `aws-exports.ts` prior to importing, or enable the `allowJs` [compiler option](https://www.typescriptlang.org/docs/handbook/compiler-options.html) in your tsconfig. </Callout> First, enable the Angular forms modules in `src/app/app.module.ts`: ```js import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AmplifyAuthenticatorModule } from '@aws-amplify/ui-angular'; /* new form imports */ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { RestaurantsComponent } from './restaurants/restaurants.component'; @NgModule({ declarations: [AppComponent, RestaurantsComponent], imports: [ BrowserModule, AppRoutingModule, AmplifyAuthenticatorModule, /* configuring form modules */ FormsModule, ReactiveFormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule {} ``` Next, create an `restaurants` Angular component: ``` npx ng generate component restaurants ``` Open `src/app/app.component.html`, and replace the default content with the Angular component you created: ```html <app-restaurants></app-restaurants> ``` In your `src/app/restaurants/restaurants.component.ts` file, add data to your database with a mutation by using the `API.service` file which was generated when you ran `amplify add api`: ```typescript import { Component } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { APIService, Restaurant } from '../API.service'; @Component({ selector: 'app-restaurants', templateUrl: './restaurants.component.html', styleUrls: ['./restaurants.component.css'] }) export class RestaurantsComponent { public createForm: FormGroup; constructor(private api: APIService, private fb: FormBuilder) { this.createForm = this.fb.group({ name: ['', Validators.required], description: ['', Validators.required], city: ['', Validators.required] }); } public onCreate(restaurant: Restaurant) { this.api .CreateRestaurant(restaurant) .then(() => { console.log('item created!'); this.createForm.reset(); }) .catch((e) => { console.log('error creating restaurant...', e); }); } } ``` Next, add a form that will be used for creating restaurants. Add the following to your `src/app/restaurants/restaurants.component.html`: ```html <div class="form-body"> <form autocomplete="off" [formGroup]="createForm" (ngSubmit)="onCreate(createForm.value)" > <div> <label>Name: </label> <input type="text" formControlName="name" autocomplete="off" /> </div> <div> <label>Description: </label> <input type="text" formControlName="description" autocomplete="off" /> </div> <div> <label>City: </label> <input type="text" formControlName="city" autocomplete="off" /> </div> <button type="submit">Submit</button> </form> </div> ``` Next, update your `RestaurantsComponent` class so that it will list all restaurants in the database when the app starts. To do so, implement [OnInit](https://angular.io/api/core/OnInit) and add a `ListRestaurants` query in `src/app/restaurants/restaurants.component.ts`. Store the query results in an array. ```typescript import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { APIService, Restaurant } from '../API.service'; @Component({ selector: 'app-restaurants', templateUrl: './restaurants.component.html', styleUrls: ['./restaurants.component.css'] }) export class RestaurantsComponent implements OnInit { public createForm: FormGroup; /* declare restaurants variable */ public restaurants: Array<Restaurant> = []; constructor(private api: APIService, private fb: FormBuilder) { this.createForm = this.fb.group({ name: ['', Validators.required], description: ['', Validators.required], city: ['', Validators.required] }); } async ngOnInit() { /* fetch restaurants when app loads */ this.api.ListRestaurants().then((event) => { this.restaurants = event.items as Restaurant[]; }); } public onCreate(restaurant: Restaurant) { this.api .CreateRestaurant(restaurant) .then((event) => { console.log('item created!'); this.createForm.reset(); }) .catch((e) => { console.log('error creating restaurant...', e); }); } } ``` Add the following to your `src/app/restaurants/restaurants.component.html` to display any of the restaurants you have added: ```html <div *ngFor="let restaurant of restaurants"> <div>{{ restaurant?.city }}</div> <div>{{ restaurant?.name }}</div> <div>{{ restaurant?.description }}</div> </div> ``` To subscribe to realtime data, declare a subscription class variable and update `ngOnInit` in `src/app/restaurants.component.ts`. When the app starts, this code will set up a subscription. The subscription will update the `restaurants` array when new events are received (when a new restaurant is created): ```typescript /** Subscription type will be inferred from this library */ import { ZenObservable } from 'zen-observable-ts'; // ... /** Declare subscription variable */ private subscription: ZenObservable.Subscription | null = null; // ... async ngOnInit() { this.api.ListRestaurants().then(event => { this.restaurants = event.items as Restaurant[]; }); /* subscribe to new restaurants being created */ this.subscription = this.api.OnCreateRestaurantListener().subscribe( (event: any) => { const newRestaurant = event.value.data.onCreateRestaurant; this.restaurants = [newRestaurant, ...this.restaurants]; } ); } ``` Finally, unsubscribe from the subscription when the component is destroyed. Import and add `OnDestroy` in `src/app/restaurants.component.ts`: ```typescript import { Component, OnDestroy, OnInit } from '@angular/core'; export class AppComponent implements OnInit, OnDestroy { // ... ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } this.subscription = null; } ``` The final `restaurants.component.ts` file should look like the following: ```typescript import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { APIService, Restaurant } from '../API.service'; /** Subscription type will be inferred from this library */ import { ZenObservable } from 'zen-observable-ts'; @Component({ selector: 'app-restaurants', templateUrl: './restaurants.component.html', styleUrls: ['./restaurants.component.css'] }) export class RestaurantsComponent implements OnInit, OnDestroy { public createForm: FormGroup; /* declare restaurants variable */ public restaurants: Array<Restaurant> = []; /** Declare subscription variable */ private subscription: ZenObservable.Subscription | null = null; constructor(private api: APIService, private fb: FormBuilder) { this.createForm = this.fb.group({ name: ['', Validators.required], description: ['', Validators.required], city: ['', Validators.required] }); } async ngOnInit() { this.api.ListRestaurants().then(event => { this.restaurants = event.items as Restaurant[]; }); /* subscribe to new restaurants being created */ this.subscription = this.api.OnCreateRestaurantListener().subscribe( (event: any) => { const newRestaurant = event.value.data.onCreateRestaurant; this.restaurants = [newRestaurant, ...this.restaurants]; } ); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } this.subscription = null; } public onCreate(restaurant: Restaurant) { this.api .CreateRestaurant(restaurant) .then((event) => { console.log('item created!'); this.createForm.reset(); }) .catch((e) => { console.log('error creating restaurant...', e); }); } } ``` Next, run the app: ```sh npm start ``` Now, open the app in 2 browser windows (both at <http://localhost:4200/>) so that you have your app running side by side. When creating a new item in one window, you should see it come through in the other window in real-time.