Building an Angular CRUD App with a Go API
CRUD operations (Create, Read, Update, Delete) are the backbone of most web applications. In this tutorial, we’ll show you how to build a CRUD app with Angular on the front end and a GoAPI on the back end, resulting in a fully integrated and efficient full-stack solution.
Prerequisites
- Node.js
- Go 1.21
- MySQL
Setup Angular project
Install Angular 18 and create a new project with the following command.
npm install -g @angular/cli@18.0.0 ng new view --minimal --routing --style css --no-standalone --ssr=false
Angular project structure
└─ src ├─ app │ ├─ app-routing.module.ts │ ├─ app.component.ts │ ├─ app.interceptor.ts │ ├─ app.module.ts │ └─ components │ └─ product │ ├─ Create.component.ts │ ├─ Delete.component.ts │ ├─ Detail.component.ts │ ├─ Edit.component.ts │ ├─ Index.component.ts │ └─ Product.service.ts ├─ index.html ├─ main.ts └─ styles.css
*This project structure will display only the files and folders that we plan to create or modify.
Angular Project files
main.ts
import { enableProdMode } from '@angular/core' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { AppModule } from './app/app.module' platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e))
This main.ts file initializes an Angular application by bootstrapping the AppModule using the platformBrowserDynamic function. It sets up the application to run in the browser and handles any errors that occur during the bootstrapping process.
app.module.ts
import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http' import { AppRoutingModule } from './app-routing.module' import { AppComponent } from './app.component' import { AppInterceptor } from './app.interceptor' import { ProductIndex } from './components/product/Index.component' import { ProductCreate } from './components/product/Create.component' import { ProductDetail } from './components/product/Detail.component' import { ProductEdit } from './components/product/Edit.component' import { ProductDelete } from './components/product/Delete.component' @NgModule({ declarations: [ AppComponent, ProductIndex, ProductCreate, ProductDetail, ProductEdit, ProductDelete, ], imports: [ BrowserModule, AppRoutingModule, FormsModule, HttpClientModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: AppInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
The AppModule is the main module of an Angular application. It imports core Angular modules and sets up routing with AppRoutingModule. The module declares various product-related components. It also registers AppInterceptor as an HTTP interceptor. The AppComponent is set as the bootstrap component, making it the entry point of the application.
app.component.ts
import { Component } from '@angular/core' @Component({ selector: 'app-root', template: `<router-outlet></router-outlet>` }) export class AppComponent { }
The app.component.ts file defines the root component, AppComponent, which uses the
app.interceptor.ts
import { Injectable } from '@angular/core'; import { HttpInterceptor } from '@angular/common/http'; import { HttpRequest, HttpErrorResponse } from '@angular/common/http' import { Observable, throwError } from 'rxjs' import { HttpHandler } from '@angular/common/http' import { HttpEvent } from '@angular/common/http' @Injectable({ providedIn: 'root' }) export class AppInterceptor implements HttpInterceptor { baseURL = 'http://localhost:8080/api' intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request.clone({ url: this.baseURL + request.url, })) } }
The AppInterceptor class is an Angular HTTP interceptor that appends a configurable baseURL to all outgoing HTTP request URLs before they are sent to the server. This allows the application to centralize and easily manage the base API endpoint.
app-routing.module.ts
import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' import { ProductIndex } from './components/product/Index.component' import { ProductCreate } from './components/product/Create.component' import { ProductDetail } from './components/product/Detail.component' import { ProductEdit } from './components/product/Edit.component' import { ProductDelete } from './components/product/Delete.component' const routes: Routes = [ { path: '', redirectTo: 'product', pathMatch: 'full' }, { path: 'product', component: ProductIndex }, { path: 'product/create', component: ProductCreate }, { path: 'product/:id', component: ProductDetail }, { path: 'product/edit/:id', component: ProductEdit }, { path: 'product/delete/:id', component: ProductDelete } ] @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
The AppRoutingModule sets up routing for an Angular application, including product-related paths for listing, creating, viewing, editing, and deleting products. It also includes a route that redirects from the root path "/" to the product listing page "/product".
Create.component.ts
import { Component } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { ProductService } from './Product.service' @Component({ selector: 'product-create', template: ` <div class="container"> <div class="row"> <div class="col"> <form ngNativeValidate method="post" (submit)="create()"> <div class="row"> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_name">Name</label> <input id="product_name" name="name" class="form-control" [(ngModel)]="product.Name" maxlength="50" /> <span *ngIf="errors.name" class="text-danger">{{errors.name}}</span> </div> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_price">Price</label> <input id="product_price" name="price" class="form-control" [(ngModel)]="product.Price" type="number" /> <span *ngIf="errors.price" class="text-danger">{{errors.price}}</span> </div> <div class="col-12"> <a class="btn btn-secondary" routerLink="/product">Cancel</a> <button class="btn btn-primary">Submit</button> </div> </div> </form> </div> </div> </div>` }) export class ProductCreate { product?: any = {} errors?: any = {} constructor(private router: Router, private route: ActivatedRoute, private ProductService: ProductService) { } create() { this.ProductService.create(this.product).subscribe(() => { this.router.navigateByUrl('/product') }, (e) => { alert(e.error) }) } }
The ProductCreate component provides a form for creating a new product, binding input fields for name and price to a product object. On submission, it calls ProductService to create the product and navigates back to the product list. Validation errors are displayed next to the corresponding fields, and any creation errors trigger an alert.
Delete.component.ts
npm install -g @angular/cli@18.0.0 ng new view --minimal --routing --style css --no-standalone --ssr=false
The ProductDelete component in Delete.component.ts is an Angular component that handles the deletion of a product. It displays a form with read-only fields showing the product's details (ID, name, and price). When the component initializes, it fetches the product details using the product ID from the route. On form submission, the delete() method calls ProductService to delete the product and then redirects to the product list. If there's an error during deletion, an alert is shown.
Detail.component.ts
└─ src ├─ app │ ├─ app-routing.module.ts │ ├─ app.component.ts │ ├─ app.interceptor.ts │ ├─ app.module.ts │ └─ components │ └─ product │ ├─ Create.component.ts │ ├─ Delete.component.ts │ ├─ Detail.component.ts │ ├─ Edit.component.ts │ ├─ Index.component.ts │ └─ Product.service.ts ├─ index.html ├─ main.ts └─ styles.css
The ProductDetail component displays the details of a specific product. It retrieves the product information based on the ID from the route and shows the product's ID, name, and price in read-only fields. The component provides "Back" and "Edit" buttons for navigation. The product details are fetched and displayed when the component initializes.
Edit.component.ts
import { enableProdMode } from '@angular/core' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { AppModule } from './app/app.module' platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e))
The ProductEdit component allows users to edit an existing product. It retrieves the product details using the product ID from the route and displays them in a form with editable fields for name and price. On form submission, it updates the product via ProductService and navigates back to the product list. Any errors during fetching or updating are shown as alerts, and validation errors are displayed next to the relevant fields.
Index.component.ts
import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http' import { AppRoutingModule } from './app-routing.module' import { AppComponent } from './app.component' import { AppInterceptor } from './app.interceptor' import { ProductIndex } from './components/product/Index.component' import { ProductCreate } from './components/product/Create.component' import { ProductDetail } from './components/product/Detail.component' import { ProductEdit } from './components/product/Edit.component' import { ProductDelete } from './components/product/Delete.component' @NgModule({ declarations: [ AppComponent, ProductIndex, ProductCreate, ProductDetail, ProductEdit, ProductDelete, ], imports: [ BrowserModule, AppRoutingModule, FormsModule, HttpClientModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: AppInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
The ProductIndex component displays a list of products in a table format. It fetches the list of products from ProductService on initialization and shows each product's ID, name, and price, with action buttons for viewing, editing, and deleting each product. It also includes a button to navigate to the product creation page.
Product.service.ts
import { Component } from '@angular/core' @Component({ selector: 'app-root', template: `<router-outlet></router-outlet>` }) export class AppComponent { }
The ProductService uses Angular's HttpClient to perform the relevant HTTP requests for product management. It provides methods to:
- get(id?): Fetch product details by ID or a list of products if no ID is provided.
- create(data?): Create a new product if data is provided, otherwise fetch the create product page.
- edit(id, data?): Update a product by ID if data is provided, otherwise fetch product details for editing.
- delete(id, data?): Delete a product by ID if data is provided, otherwise fetch product details for confirmation.
styles.css
import { Injectable } from '@angular/core'; import { HttpInterceptor } from '@angular/common/http'; import { HttpRequest, HttpErrorResponse } from '@angular/common/http' import { Observable, throwError } from 'rxjs' import { HttpHandler } from '@angular/common/http' import { HttpEvent } from '@angular/common/http' @Injectable({ providedIn: 'root' }) export class AppInterceptor implements HttpInterceptor { baseURL = 'http://localhost:8080/api' intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request.clone({ url: this.baseURL + request.url, })) } }
The CSS adjusts the layout by adding space above the container and spacing out buttons horizontally.
index.html
import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' import { ProductIndex } from './components/product/Index.component' import { ProductCreate } from './components/product/Create.component' import { ProductDetail } from './components/product/Detail.component' import { ProductEdit } from './components/product/Edit.component' import { ProductDelete } from './components/product/Delete.component' const routes: Routes = [ { path: '', redirectTo: 'product', pathMatch: 'full' }, { path: 'product', component: ProductIndex }, { path: 'product/create', component: ProductCreate }, { path: 'product/:id', component: ProductDetail }, { path: 'product/edit/:id', component: ProductEdit }, { path: 'product/delete/:id', component: ProductDelete } ] @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
The HTML serves as the main entry point for an Angular application, including Bootstrap for styling, Font Awesome for icons, and
Setup Go API project
npm install -g @angular/cli@18.0.0 ng new view --minimal --routing --style css --no-standalone --ssr=false
Create a testing database named "example" and execute the database.sql file to import the table and data.
Go API Project structure
└─ src ├─ app │ ├─ app-routing.module.ts │ ├─ app.component.ts │ ├─ app.interceptor.ts │ ├─ app.module.ts │ └─ components │ └─ product │ ├─ Create.component.ts │ ├─ Delete.component.ts │ ├─ Detail.component.ts │ ├─ Edit.component.ts │ ├─ Index.component.ts │ └─ Product.service.ts ├─ index.html ├─ main.ts └─ styles.css
Go API Project files
.env
import { enableProdMode } from '@angular/core' import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import { AppModule } from './app/app.module' platformBrowserDynamic().bootstrapModule(AppModule).catch(e => console.error(e))
This file holds the configuration details for connecting to the database.
db.go
import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http' import { AppRoutingModule } from './app-routing.module' import { AppComponent } from './app.component' import { AppInterceptor } from './app.interceptor' import { ProductIndex } from './components/product/Index.component' import { ProductCreate } from './components/product/Create.component' import { ProductDetail } from './components/product/Detail.component' import { ProductEdit } from './components/product/Edit.component' import { ProductDelete } from './components/product/Delete.component' @NgModule({ declarations: [ AppComponent, ProductIndex, ProductCreate, ProductDetail, ProductEdit, ProductDelete, ], imports: [ BrowserModule, AppRoutingModule, FormsModule, HttpClientModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: AppInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule { }
This db.go file configures the database connection with GORM. The SetupDatabase function loads environment variables, constructs a MySQL connection string, and initializes a GORM instance, which is stored in the global DB variable.
router.go
import { Component } from '@angular/core' @Component({ selector: 'app-root', template: `<router-outlet></router-outlet>` }) export class AppComponent { }
This router.go file sets up routing for a Go application using the Gin framework. The SetupRouter function initializes a Gin router with CORS middleware to allow all origins. It defines routes for handling product-related operations under the /api/products path, each mapped to a method in the ProductController. Finally, it starts the Gin server.
product.go
import { Injectable } from '@angular/core'; import { HttpInterceptor } from '@angular/common/http'; import { HttpRequest, HttpErrorResponse } from '@angular/common/http' import { Observable, throwError } from 'rxjs' import { HttpHandler } from '@angular/common/http' import { HttpEvent } from '@angular/common/http' @Injectable({ providedIn: 'root' }) export class AppInterceptor implements HttpInterceptor { baseURL = 'http://localhost:8080/api' intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request.clone({ url: this.baseURL + request.url, })) } }
This product.go file defines a Product model for use with GORM. It specifies a Product struct with three fields: Id (an auto-incrementing primary key), Name, Price.
product_controller.go
import { NgModule } from '@angular/core' import { RouterModule, Routes } from '@angular/router' import { ProductIndex } from './components/product/Index.component' import { ProductCreate } from './components/product/Create.component' import { ProductDetail } from './components/product/Detail.component' import { ProductEdit } from './components/product/Edit.component' import { ProductDelete } from './components/product/Delete.component' const routes: Routes = [ { path: '', redirectTo: 'product', pathMatch: 'full' }, { path: 'product', component: ProductIndex }, { path: 'product/create', component: ProductCreate }, { path: 'product/:id', component: ProductDetail }, { path: 'product/edit/:id', component: ProductEdit }, { path: 'product/delete/:id', component: ProductDelete } ] @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
The product_controller.go file defines a ProductController struct with methods to handle CRUD operations for products in a Go application using the Gin framework.
- Index Retrieves and returns a list of all products.
- Get Fetches and returns a single product by its ID.
- Create Creates a new product from the request body.
- Update Updates an existing product with the data from the request body.
- Delete Deletes a product by its ID.
main.go
import { Component } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { ProductService } from './Product.service' @Component({ selector: 'product-create', template: ` <div class="container"> <div class="row"> <div class="col"> <form ngNativeValidate method="post" (submit)="create()"> <div class="row"> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_name">Name</label> <input id="product_name" name="name" class="form-control" [(ngModel)]="product.Name" maxlength="50" /> <span *ngIf="errors.name" class="text-danger">{{errors.name}}</span> </div> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_price">Price</label> <input id="product_price" name="price" class="form-control" [(ngModel)]="product.Price" type="number" /> <span *ngIf="errors.price" class="text-danger">{{errors.price}}</span> </div> <div class="col-12"> <a class="btn btn-secondary" routerLink="/product">Cancel</a> <button class="btn btn-primary">Submit</button> </div> </div> </form> </div> </div> </div>` }) export class ProductCreate { product?: any = {} errors?: any = {} constructor(private router: Router, private route: ActivatedRoute, private ProductService: ProductService) { } create() { this.ProductService.create(this.product).subscribe(() => { this.router.navigateByUrl('/product') }, (e) => { alert(e.error) }) } }
This main.go file is the entry point for the Go application. It imports configuration and routing packages, then calls config.SetupDatabase()to initialize the database connection and router.SetupRouter() to set up the application’s routes.
Run projects
Run Angular project
import { Component } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { ProductService } from './Product.service' @Component({ selector: 'product-delete', template: ` <div class="container"> <div class="row"> <div class="col"> <form ngNativeValidate method="post" (submit)="this.delete()"> <div class="row"> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_id">Id</label> <input readonly id="product_id" name="id" class="form-control" value="{{product.Id}}" type="number" required /> </div> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_name">Name</label> <input readonly id="product_name" name="name" class="form-control" value="{{product.Name}}" maxlength="50" /> </div> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_price">Price</label> <input readonly id="product_price" name="price" class="form-control" value="{{product.Price}}" type="number" /> </div> <div class="col-12"> <a class="btn btn-secondary" routerLink="/product">Cancel</a> <button class="btn btn-danger">Delete</button> </div> </div> </form> </div> </div> </div>` }) export class ProductDelete { product?: any = {} constructor(private router: Router, private route: ActivatedRoute, private ProductService: ProductService) { } ngOnInit() { this.get() } get() { return this.ProductService.delete(this.route.snapshot.params['id']).subscribe(data => { this.product = data }, e => { alert(e.error) }) } delete() { this.ProductService.delete(this.route.snapshot.params['id'], this.product).subscribe(() => { this.router.navigateByUrl('/product') }, (e) => { alert(e.error) }) } }
Run Go API project
import { Component } from '@angular/core' import { ActivatedRoute } from '@angular/router' import { ProductService } from './Product.service' @Component({ selector: 'product-detail', template: ` <div class="container"> <div class="row"> <div class="col"> <form ngNativeValidate method="post"> <div class="row"> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_id">Id</label> <input readonly id="product_id" name="id" class="form-control" value="{{product.Id}}" type="number" required /> </div> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_name">Name</label> <input readonly id="product_name" name="name" class="form-control" value="{{product.Name}}" maxlength="50" /> </div> <div class="mb-3 col-md-6 col-lg-4"> <label class="form-label" for="product_price">Price</label> <input readonly id="product_price" name="price" class="form-control" value="{{product.Price}}" type="number" /> </div> <div class="col-12"> <a class="btn btn-secondary" routerLink="/product">Back</a> <a class="btn btn-primary" routerLink="/product/edit/{{product.Id}}">Edit</a> </div> </div> </form> </div> </div> </div>` }) export class ProductDetail { product?: any = {} constructor(private route: ActivatedRoute, private ProductService: ProductService) { } ngOnInit() { this.get() } get() { return this.ProductService.get(this.route.snapshot.params['id']).subscribe(data => { this.product = data }, e => { alert(e.error) }) } }
Open the web browser and goto http://localhost:4200
You will find this product list page.
Testing
Click the "View" button to see the product details page.
Click the "Edit" button to modify the product and update its details.
Click the "Submit" button to save the updated product details.
Click the "Create" button to add a new product and input its details.
Click the "Submit" button to save the new product.
Click the "Delete" button to remove the previously created product.
Click the "Delete" button to confirm the removal of this product.
Conclusion
In conclusion, we have learned how to create a basic Angular project with components, views, and routing, while setting up an API using the Gin framework as the backend. By utilizing GORM for database operations, we've successfully built a dynamic front-end that seamlessly integrates with a powerful and efficient backend. This combination forms a solid foundation for developing modern, full-stack web applications.
Source code: https://github.com/stackpuz/Example-CRUD-Angular-18-Go
Create an Angular CRUD App in Minutes: https://stackpuz.com
The above is the detailed content of Building an Angular CRUD App with a Go API. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

OpenSSL, as an open source library widely used in secure communications, provides encryption algorithms, keys and certificate management functions. However, there are some known security vulnerabilities in its historical version, some of which are extremely harmful. This article will focus on common vulnerabilities and response measures for OpenSSL in Debian systems. DebianOpenSSL known vulnerabilities: OpenSSL has experienced several serious vulnerabilities, such as: Heart Bleeding Vulnerability (CVE-2014-0160): This vulnerability affects OpenSSL 1.0.1 to 1.0.1f and 1.0.2 to 1.0.2 beta versions. An attacker can use this vulnerability to unauthorized read sensitive information on the server, including encryption keys, etc.

Backend learning path: The exploration journey from front-end to back-end As a back-end beginner who transforms from front-end development, you already have the foundation of nodejs,...

The library used for floating-point number operation in Go language introduces how to ensure the accuracy is...

Queue threading problem in Go crawler Colly explores the problem of using the Colly crawler library in Go language, developers often encounter problems with threads and request queues. �...

The difference between string printing in Go language: The difference in the effect of using Println and string() functions is in Go...

Under the BeegoORM framework, how to specify the database associated with the model? Many Beego projects require multiple databases to be operated simultaneously. When using Beego...

The problem of using RedisStream to implement message queues in Go language is using Go language and Redis...

What should I do if the custom structure labels in GoLand are not displayed? When using GoLand for Go language development, many developers will encounter custom structure tags...
