This project runs live (see link below). The frontend is hosted by Netlify, while the backend is hosted by Vercel.
Data and images are managed via PostgreSQL database on Neon and cloudstorage by Cloudflare.
For testing purposes, one instance runs on env:stag, while the live version runs on env:prod.
visit the WEBSITE
- node: v20+
- Cloudstorage (R2 Object Storage)
- Betterstack Telemetry (logging)
Download or clone project
git clone https://github.com/yqni13/artcreation-dvCreate new .env file and fill in your credentials/other env data (see docs).
Navigate/cd into the root paths (/frontend and /backend) and install dependencies via npm:
$ npm ciAdditionally, the script to overwrite env data needs to be configured within set-env.ts (env:prod). Create a local copy (see docs) for local development. Start application (frontend) in local environment:
$ npm run startwhich will open automatically on http://localhost:4200/. To run backend, use:
$ node server.js| Feature | Description |
|---|---|
| 🪁 Angular | Angular v19 standalone with routing + nested routes on id |
| 📂 Cloud file handling | Upload/delete via Cloudflare R2 (S3-compatible) - supporting images up to 4MB |
| 🖼️ Preload | Customized image/video preload on page loading + viewport/scroll |
| ⌨ Input | Customized form components (text/textarea/select) |
| 🎠 Carousel | Customized carousel component |
| 🎨 Themes | Customized theme options (dark/light mode) |
| Customized validation in combination with Angular + Express-Validator | |
| Email service with node.js & nodemailer | |
| 🗯️ Notification | Customized snackbar + extended translation service |
| 📱 Design | Responsive design 400px > width < 1800px supported via flexbox & media queries |
To contact the artist for product orders or general inquiries, a custom form is provided. Currently, this feature is not available in the live version due to the lack of backend hosting.
The form combines customized components, frontend/backend validation logic, nodemailer, and a Node.js service to send emails via a predefined no-reply account.
Validation checks ensure all required fields are filled, that the reference number follows the correct format, and that a valid selection has been made.
Figure 2 shows an example validation message:
"ReferenceNr '561H65' does not exist".
To reach an international audience of artists and art enthusiasts, the webpage was initially developed in English, with support for multiple languages implemented using the ngx-translate package (@ngx-translate/core + @ngx-translate/http-loader).
Currently, two languages are available: English and German (see Figure 3).
Static and dynamic texts are translated based on the selection made in the footer. The selected language is saved in localstorage, similar to the color theme, and persists between visits.
To simplify translation maintenance, the TranslateHttpLoader was customized to combine multiple .json files per language instead of the default single-file approach.
See custom translate loader for implementation details.
In case of unexpected responses or to visually confirm actions, a custom snackbar appears at the top right (or centered on screens smaller than 500px). The snackbar can be triggered with just two required inputs (title + type), and optionally extended with up to five configuration parameters.
To enhance contrast, each of the four message types uses a distinct color:
Figure 4 shows an example error message indicating that the email could not be sent (highlighted in red), triggered by an HTTP interceptor when no backend is available.
Instead of Angular’s built-in @defer blocks, this application uses a custom lazy loading strategy via HostListeners.
When opening the Gallery component, all images currently within the viewport are rendered immediately. Additionally, a buffer of images just below the viewport is preloaded to ensure a smooth scrolling experience. As the user scrolls, more images are continuously preloaded.
Figure 5 illustrates how 6 images inside the viewport, along with the next 3 rows of images, are shown as preloaded in the network tab of DevTools.
The webpage offers two theme settings:
Most images have additional logic attached for either displaying more details or scaling them up. In the news archive, additional text content is shown; clicking the magnifier icon displays the image in full resolution.
In the gallery section, preview thumbnails open in a museum-style view, showing all available details about the artwork. Clicking the image again displays it at maximum resolution (see Figure 7).
PostgreSQL is used as the relational database management system to store all necessary data. The free-tier plan from the Neon hosting service is sufficient to handle all data in this context (see Figure 8, basic findAll request).
Migrations are handled with the node package node-pg-migrate in the backend (see docs)).
Image files for "gallery" and "news" elements are not stored in the database; only their paths are saved within the respective entries (see Figure 8, response). Additionally, files submitted by users (for "gallery" or "news" items) are processed (regarding format and size) and uploaded to a cloud object storage, in this case an R2 bucket from Cloudflare.
Once the data is retrieved from the database, the saved image path is concatenated with a cloud-specific access URL to form the full image URL (see Figure 9, red highlight). The images are then loaded from the cloud or cache.
Data related to the "gallery" and "news" elements can be managed by the administrator via login and a dedicated administration area. While "gallery" data is accessible through the "gallery" section in the navigation bar, "news" items are displayed on the "home" page and in the archive. On the "home" page, the three latest "news" entries are presented using a carousel component.
The archive lists all "news" entries in descending chronological order. Features such as text-based search and sorting will be available in a future version.
The application supports single-file uploads (maximum 4MB) from the user’s local device to associate an image with a newly created item. Alternatively, a "news" item can be linked to an existing "gallery" entry (see Figure 11: select an existing artwork). Using a foreign key referencing the linked "gallery" item's ID, a LEFT JOIN SQL query is used to retrieve the necessary and up-to-date data.
SELECT
${tableNews}.*,
${tableGallery}.image_path AS image_path_${tableGallery},
${tableGallery}.thumbnail_path AS thumbnail_path_${tableGallery},
${tableGallery}.reference_nr AS reference_nr_${tableGallery},
${tableGallery}.art_genre AS art_genre_${tableGallery}
FROM ${tableNews}
LEFT JOIN ${tableGallery} ON ${tableNews}.gallery = ${tableGallery}.gallery_id
ORDER BY ${orderPrio1} DESCThe provided image paths allow images to be loaded without additional database queries. The reference number and art genre offer sufficient information to navigate directly from a news article to the corresponding artwork details (see Figure 10).
Currently, for the UI, only console logs and snackbar notifications inform users about errors and warnings. For the backend, the logging framework Winston is used in combination with Logtail from BetterStack for easy access and long-term storage (see Figure 11, test phase).
The jest testing framework was added to the project, providing unit-tests and integration-tests for the backend.
Install the packages @jest/globals, @types/jest and supertest in addition to jest:
npm install jest @jest/globals @types/jest supertest --save-devOnly some basic tests exist currently for utils (see tests).
Run tests on local device by including setup for dotenv/config to provide environment variables:
set MODE=staging && jest --setupFiles dotenv/configor simply save as script command in package.json to run npm test:
"scripts": {
"start": "node server.js",
"test": "set MODE=staging && jest --setupFiles dotenv/config"
}To automatically run tests before merging a feature/development branch upstream, a GitHub Action is set up (see main.yml).
To prevent an unwanted merge due to an unfinished or failed test run, the project is set up to disable merging until all tests have passed (see Figure 12).
| Firefox | Chrome | Opera | Edge | DuckGo | Brave |
| Yes* | Yes | Yes | Yes | Yes | Yes |
*This browser has problems with some functionality.
Angular ESLint was added to the project as the next step of testing.
Install ESLint global via node package manager:
$ npm install -g eslintInstall ESLint local for angular project:
$ ng add @angular-eslint/schematicsRun ESLint to list all current lint errors:
$ npm run lint-
$\textsf{\color{black}Deletion:}$ Deleted files related to the docker configuration. -
$\textsf{\color{orange}Patch:}$ Updated:- fn reset(...) (support component) does not change support option on manual reset.
- extracted fn scrollToTop(...) to navigation service.
- - pagination in archive component
- - provide security standards: input sanitizations, content security policies & HttpOnly cookies
- - deploy a Web Application Manifest to make webpage into a progressive web app (PWA)
- - direct pay option












