diff --git a/README.md b/README.md index 5f005a7..56837e2 100644 --- a/README.md +++ b/README.md @@ -58,4 +58,4 @@ This project is licensed under the MIT License - see the [LICENSE](./LICENSE) fi ## Stay in touch -- Author - [Enoch Osarenren](www.linkedin.com/in/enoch-osarenren) +- Author - [Enoch Osarenren](https://linkedin.com/in/enoch-osarenren) diff --git a/package-lock.json b/package-lock.json index 6c50557..e01ef71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "json-placeholder-api-nestjs-graphqlhub", + "name": "graphql-placeholder", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "json-placeholder-api-nestjs-graphqlhub", + "name": "graphql-placeholder", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { @@ -23,6 +23,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "graphql": "^16.8.1", + "hbs": "^4.2.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "ts-morph": "^21.0.1" @@ -5682,6 +5683,11 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreachasync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", + "integrity": "sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==" + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -6043,6 +6049,34 @@ "graphql": ">=0.11 <=16" } }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -6126,6 +6160,19 @@ "node": ">= 0.4" } }, + "node_modules/hbs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hbs/-/hbs-4.2.0.tgz", + "integrity": "sha512-dQwHnrfWlTk5PvG9+a45GYpg0VpX47ryKF8dULVd6DtwOE6TEcYQXQ5QM6nyOx/h7v3bvEQbdn19EDAcfUAgZg==", + "dependencies": { + "handlebars": "4.7.7", + "walk": "2.3.15" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -8159,8 +8206,7 @@ "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/node-abort-controller": { "version": "3.1.1", @@ -10409,6 +10455,18 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", @@ -10581,6 +10639,14 @@ "node": ">= 0.8" } }, + "node_modules/walk": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.15.tgz", + "integrity": "sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg==", + "dependencies": { + "foreachasync": "^3.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -10797,6 +10863,11 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index d960e98..cb9421e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "graphql": "^16.8.1", + "hbs": "^4.2.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "ts-morph": "^21.0.1" diff --git a/public/become_a_patron_button.png b/public/become_a_patron_button.png new file mode 100644 index 0000000..291b7bc Binary files /dev/null and b/public/become_a_patron_button.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..bfdd40d Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..e0a6fb8 --- /dev/null +++ b/public/style.css @@ -0,0 +1,202 @@ +body { + font-family: system-ui, BlinkMacSystemFont, -apple-system, Segoe UI, Roboto, + Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + color: rgba(0, 0, 0, 0.84); + line-height: 1.5; +} + +a { + color: #1e87f0; + text-decoration: none; + border-bottom: 1px solid transparent; +} + +div.announcement { + display: block; + padding: 1rem; + font-weight: bold; + border-width: 0; + border-style: solid; + border-color: currentColor; + text-align: center; + background: #ffe0b2; + color: #e65100; +} + +div.announcement a { + color: inherit; + text-decoration: underline; +} + +div.announcement a:hover { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +h1 { + margin: 0; + font-size: 4rem; + font-weight: 300; + letter-spacing: 0.05em; +} + +@media (max-width: 768px) { + h1 { + font-size: 2rem; + } +} + +h2 { + margin-top: 6rem; + margin-bottom: 1.5rem; + font-size: 2rem; + font-weight: normal; +} + +header { + position: sticky; + top: 0; + background: #ffffff; + z-index: 999; +} + +nav { + margin: 0; + overflow-x: auto; + text-align: center; + border-width: 0 0 1px 0; + border-color: #e4e4e4; + border-style: solid; +} + +nav ul { + display: flex; + margin: 0; + padding: 0; + list-style: none; +} + +nav li:first-of-type { + flex-grow: 1; + text-align: left; +} + +nav li:first-of-type a { + padding-left: 0; +} + +nav li:last-of-type a { + padding-right: 0; +} + +nav li a { + display: block; + padding: 1.5rem; + color: inherit; + white-space: nowrap; +} + +.hljs { + padding: 1rem; +} + +td:first-child { + width: 8rem; +} + +.container { + max-width: 60rem; + margin: auto; + padding: 0 1rem; +} + +.hero { + padding: 6rem 0; + text-align: center; +} + +.sponsors { + margin-bottom: 2rem; + padding: 6rem 0; + border-width: 1px 0; + border-style: solid; + border-color: #e4e4e4; + text-align: center; +} + +.sponsors h3 { + font-size: 1.5rem; + font-weight: normal; + margin-top: 0; + margin-bottom: 2rem; +} + +.box { + display: flex; + justify-content: center; + align-items: center; + width: 20rem; + margin: auto; + border-radius: 0.2rem; + border: 1px dashed currentColor; +} + +.box a { + display: inline-block; + padding: 4rem; + color: inherit; +} + +.sponsors p { + margin-top: 2rem; + margin-bottom: 0; +} + +main { + margin-bottom: 6rem; +} + +#run-button { + padding: 0.5rem 1rem; + margin-right: 1rem; + border-width: 0; + border-radius: 0.25rem; + background-color: #1e87f0; + color: white; + cursor: pointer; + transition: all 0.25s; +} + +#run-message { + opacity: 0; + transition: all 0.25s; +} + +pre { + /* hack */ + background: #f8f8f8; +} + +code { + padding: 2rem !important; +} + +ul { + list-style: none; +} + +#result { + margin: 1.5rem 0 0; + /* height: 176px; */ + opacity: 0; + transition: all 0.25s; +} + +footer { + padding: 6rem; + background-color: #f8fafc; + text-align: center; +} \ No newline at end of file diff --git a/src/app.controller.ts b/src/app.controller.ts index f8d2cba..078d5aa 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, Render } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() @@ -6,7 +6,14 @@ export class AppController { constructor(private readonly appService: AppService) {} @Get() - getHello(): string { - return this.appService.getHello() + @Render('index') + root() { + return { message: this.appService.getHello() } + } + + @Get('guide') + @Render('guide') + guide() { + return { message: this.appService.getHello() } } } diff --git a/src/main.ts b/src/main.ts index 4a50022..72c65b7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,16 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { graphqlMiddleware } from './common/middlewares/graphql.middleware'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { join } from 'path'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule); + + app.useStaticAssets(join(__dirname, '..', 'public')); + app.setBaseViewsDir(join(__dirname, '..', 'views')); + app.setViewEngine('hbs'); + app.use(graphqlMiddleware) await app.listen(3000); } diff --git a/views/guide.hbs b/views/guide.hbs new file mode 100644 index 0000000..5c0bb67 --- /dev/null +++ b/views/guide.hbs @@ -0,0 +1,292 @@ + + + + + + + + + + + + + JSONPlaceholder - Fake online REST API for developers + + + +
+
+ This service is maintained and provided for free, please consider + supporting it on + GitHub Sponsors + ❤️ +
+ +
+ +
+

+

+

+

Guide

+

You can use JSONPlaceholder with any type of project that needs to get JSON data (React, Vue, Node, + Rails, Swift, Android, …).

+

Below you'll find examples using Fetch API. You can copy paste + them in your browser Console to quickly test JSONPlaceholder.

+

Get a resource

+
fetch('https://jsonplaceholder.typicode.com/posts/1')
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+// Output
+{
+  id: 1,
+  title: '[...]',
+  body: '[...]',
+  userId: 1
+}
+
+
+ +

List all resources

+
fetch('https://jsonplaceholder.typicode.com/posts')
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+// Output
+[
+  { id: 1, title: '[...]' /* ... */ },
+  /* ... */
+  { id: 100, title: '[...]' /* ... */ }
+]
+
+

Create a resource

+
fetch('https://jsonplaceholder.typicode.com/posts', {
+    method: 'POST',
+    body: JSON.stringify({
+      title: 'foo',
+      body: 'bar',
+      userId: 1
+    }),
+    headers: {
+      "Content-type": "application/json; charset=UTF-8"
+    }
+  })
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+// Output
+{
+  id: 101,
+  title: 'foo',
+  body: 'bar',
+  userId: 1
+}
+
+

Important: the resource will not be really created on the server but it will be faked as if. In other + words, if you try to access a post using 101 as an id, you'll get a 404 error.

+

Update a resource

+

With PUT

+
fetch('https://jsonplaceholder.typicode.com/posts/1', {
+    method: 'PUT',
+    body: JSON.stringify({
+      id: 1,
+      title: 'foo',
+      body: 'bar',
+      userId: 1
+    }),
+    headers: {
+      "Content-type": "application/json; charset=UTF-8"
+    }
+  })
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+// Output
+{
+  id: 1,
+  title: 'foo',
+  body: 'bar',
+  userId: 1
+}
+
+

With PATCH

+
fetch('https://jsonplaceholder.typicode.com/posts/1', {
+    method: 'PATCH',
+    body: JSON.stringify({
+      title: 'foo'
+    }),
+    headers: {
+      "Content-type": "application/json; charset=UTF-8"
+    }
+  })
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+// Output
+{
+  id: 1,
+  title: 'foo',
+  body: '[...]',
+  userId: 1
+}
+
+

Important: the resource will not be really updated on the server but it will be faked as if.

+

Delete a resource

+
fetch('https://jsonplaceholder.typicode.com/posts/1', {
+  method: 'DELETE'
+})
+
+

Important: the resource will not be really deleted on the server but it will be faked as if.

+

Filter resources

+

Basic filtering is supported through query parameters.

+
// Will return all the posts that belong to the first user
+fetch('https://jsonplaceholder.typicode.com/posts?userId=1')
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+

Nested resources

+

One level of nested route is available.

+
// Equivalent to /comments?postId=1
+fetch('https://jsonplaceholder.typicode.com/posts/1/comments')
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+

Available nested routes:

+ +

+

+

+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/views/index.hbs b/views/index.hbs new file mode 100644 index 0000000..44ab0fd --- /dev/null +++ b/views/index.hbs @@ -0,0 +1,397 @@ + + + + + + + + + + + + + QraphQLPlaceholder - Fake online GraphQL API for developers + + + +
+
+ This service is maintained and provided for free, please consider + supporting it on + GitHub Sponsors + ❤️ +
+ +
+ +
+ +
+
+

+ QraphQLPlaceholder +

+ +

+ {{!-- Fake Online GraphQL API for Testing and Prototyping --}} + Your Sandbox for Seamless GraphQL Testing and Prototyping + {{!--
Serving ~350M requests per month --}} +
Powered by + JSONPlaceholder + {{!-- + + LowDB --}} +

+ + {{!--

+ + + +

--}} +
+
+ + +
+

Sponsors

+ + {{!--

+ + + +

+ +

+ + + +

--}} + +

+ Your Company Logo Here +

+
+ + +
+
+ +

Welcome

+

+ GraphQLPlaceholder is a free online GraphQL implementation of the popular JSON Placeholder RESTful API. GraphQLPlaceholder offers a dynamic GraphQL playground designed for testing and experimentation, providing developers with an easy-to-use platform for generating fake data on demand. +
+
+ Whether you're working on tutorials, testing new libraries, or sharing code examples, GraphQLPlaceholder is an invaluable tool for developers looking to streamline their workflows and enhance their coding practices. It can be used in a README on GitHub, for a demo on CodeSandbox, in code examples on Stack Overflow, or simply to test things locally. +

+ + +

Try it

+

+ Run this code in a console or from any site: +

+ +
const query = `
+    query {
+        userById(id: 1) {
+            id
+            name
+            email
+            phone
+        }
+    }
+`;
+
+fetch('https://graphqlplaceholder.vercel.app/graphql', {
+    method: 'POST',
+    headers: {
+        'Content-Type': 'application/json'
+    },
+    body: JSON.stringify({ query })
+})
+  .then(response => response.json())
+  .then(json => console.log(json))
+
+ +

+ +

+ +
+ Congrats you've made your first call to GraphQLPlaceholder! 😃 🎉 + +

+ Tip: you can use + + http:// + or + + https:// + when making requests to GraphQLPlaceholder. +

+ +
+ + + +

Resources

+

+ JSONPlaceholder comes with a set of 6 common resources: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ /posts + 100 posts
+ /comments + 500 comments
+ /albums + 100 albums
+ /photos + 5000 photos
+ /todos + 200 todos
+ /users + 10 users
+ +

+ Note: resources have relations. For example: + posts have many + comments, + albums have many + photos, ... see below for routes examples. +

+ + +

Routes

+

+ All HTTP methods are supported. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GET + /posts +
GET + /posts/1 +
GET + /posts/1/comments +
GET + /comments?postId=1 +
GET + /posts?userId=1 +
POST/posts
PUT/posts/1
PATCH/posts/1
DELETE/posts/1
+ +

+ Note: you can view detailed examples + here. +

+ + +

Use your own data

+ + +

+ With My JSON Server online service and a simple + GitHub repo, you can have your own online fake REST server in seconds. +

+
+
+ +
+ + + + + + + + + + + + \ No newline at end of file