https://github.com/idkjs/whatsapp-clone-server
https://github.com/idkjs/whatsapp-clone-server
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/idkjs/whatsapp-clone-server
- Owner: idkjs
- Created: 2020-05-19T18:58:21.000Z (about 5 years ago)
- Default Branch: with-graphql
- Last Pushed: 2023-01-06T06:24:41.000Z (over 2 years ago)
- Last Synced: 2025-02-04T16:51:07.859Z (5 months ago)
- Language: TypeScript
- Homepage:
- Size: 1.51 MB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 15
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# WhatsAppServer
## [CORS](https://www.tortilla.academy/Urigo/WhatsApp-Clone-Tutorial/master/next/step/3)
> Unlike the previous route, we used the .json() method this time around to send a response. This will simply stringify the given JSON and set the right headers. Similarly to the client, we've defined the db mock in a dedicated file, as this is easier to maintain and look at.
> It's also recommended to connect a middleware called cors which will enable cross-origin requests. Without it we will only be able to make requests in localhost, something which is likely to limit us in the future because we would probably host our server somewhere separate than the client application. Without it it will also be impossible to call the server from our client app. Let's install the cors library and load it with the Express middleware() function:
```sh
$ yarn add cors# and its Typescript types:
$ yarn add --dev @types/cors
```## Use CORS
```ts
import cors from 'cors';
import express from 'express';
import { chats } from './db';const app = express();
app.use(cors());
app.get('/_ping', (req, res) => {
res.send('pong');
});app.get('/chats', (req, res) => {
res.json(chats);
});const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
```## Client Step 3.1: Define server URL
Add `.env` in root with `REACT_APP_SERVER_URL=http://localhost:4000` in your client app.
## How `REACT_APP_SERVER_URL` Works
> This will make our server's URL available under the process.env.REACT_APP_SERVER_URL member expression and it will be replaced with a fixed value at build time, just like macros. The .env file is a file which will automatically be loaded to process.env by the dotenv NPM package. react-scripts then filters environment variables which have a REACT_APP_ prefix and provides the created JSON to a Webpack plugin called DefinePlugin, which will result in the macro effect.
# [ADDING GRAPHQL](https://www.tortilla.academy/Urigo/WhatsApp-Clone-Tutorial/master/next/step/4)
## [Graphql Inspector](https://graphql-inspector.com/docs/installation)
`npm install --global @graphql-inspector/cli graphql`
```yml
- name: GraphQL Inspector
uses: kamilkisiela/[email protected]
```## Custom Scalars
To define a concrete data without any inner schema definition, scalars are used in GraphQL. For example; Int, Float, Date, URL and EmailAddress.
The supported built-in scalar types in GraphQL are mostly primitive types:
- Int: Signed 32‐bit integer
- Float: Signed double-precision floating-point value
- String: UTF‐8 character sequence
- Boolean: true or false
- ID (serialized as String): A unique identifier, often used to refetch an object or as the key for a cache. While serialized as a String, ID signifies that it is not intended to be human‐readableAny custom scalar can be declared with the scalar keyword, and custom types can be declared with the type keyword. Scalars have their own internal validation, parsing and serialization logics.
For example, you can have a custom scalar type DateTime. Due to JSON format restrictions, date objects are passed in string timestamp format or a number in milliseconds. Instead of having parsing, serialization, and validation logic in our business logic, we can define a DateTime scalar and put everything on it. So, we will have parsed native JavaScript Date object in our business logic while it is transferred as string or number like before.
[](https://medium.com/the-guild/graphql-scalars-1-0-is-out-more-types-data-integrity-and-strict-validations-on-graphql-972079428fb)
## Server Step 2.2: Create a basic GraphQL schema
```graphql
scalar Date
scalar URLtype Message {
id: ID!
content: String!
createdAt: Date!
}type Chat {
id: ID!
name: String!
picture: URL
lastMessage: Message
}type Query {
chats: [Chat!]!
}
```## Server Step 2.2: Create a basic GraphQL schema
`yarn add graphql-tools graphql-import graphql-scalars apollo-server-express graphql`
`yarn add --dev @types/graphql``graphql-tools`is a library with a set of utilities that will help us create a schema that will be compatible with Apollo's API:
```ts
// schema/resolvers.ts
import { DateTimeResolver, URLResolver } from 'graphql-scalars';
import { chats } from '../db';const resolvers = {
Date: DateTimeResolver,
URL: URLResolver,Query: {
chats() {
return chats;
},
},
};export default resolvers;
```## Server Step 2.3: Resolve Chat.lastMessage
There's one optimization however, that we should make in our DB. Right now, each chat document has a direct reference to a message via the lastMessage field. Practically speaking, this is NOT how the data sits in the DB. The lastMessage should only holds the ID for the correlated message, and then in the Node.JS app, we should resolve it according to our needs. Let's make the appropriate changes in the resolver of the lastMessage field:
modify `schema/resolvers.ts`
```ts
import { DateTimeResolver, URLResolver } from 'graphql-scalars';
import { chats, messages } from '../db';const resolvers = {
Date: DateTimeResolver,
URL: URLResolver,Chat: {
lastMessage(chat: any) {
return messages.find(m => m.id === chat.lastMessage);
},
},Query: {
chats() {
return chats;
},
},
};export default resolvers;
```**The first argument of the resolver is the raw chat data received by the `parent` of that resolver field `(chats resolver)`, and the returned result should be the mapped value which we would like to return to the client.**
Assuming that the server is running, we can already test our GraphQL endpoint. Because it's exposed to us via a REST endpoint, we can use a $ curl command to send a request to GET localhost:4000/graphql and get a response with all the data. Again, the query that we're gonna use to fetch the chats is:
```graphql
chats {
id
name
picture
lastMessage {
id
content
createdAt
}
}
```The one-liner version of it with a `$ curl` command looks like so:
```sh
curl \
-X POST \
-H "Content-Type: application/json" \
--data '{ "query": "{ chats { id name picture lastMessage { id content createdAt } } }" }' \
localhost:4000/graphql
```The one-liner version of it with a [`$graphqurl`](https://github.com/hasura/graphqurl) command looks like so:
```sh
gq http://localhost:4000/graphql \
-q 'query { chats { id name picture lastMessage { id content createdAt } } }'
```# Testing
This will basically install Jest and make it useable with TypeScript. Install
`yarn add --dev jest @types/jest ts-jest`.In addition, we will need to specify the file pattern that we would like to transform with ts-jest, by adding the following sections to `package.json`:
```json
{
"scripts": {
"test": "jest"
},
"jest": {
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "/node_modules/ts-jest"
}
}
}
```## Testing Chats with Mocks
Now we're gonna test the chats query in our GraphQL schema. To do so, we will setup an Apollo Client and send a query request to our back-end, and then we will match the received response with a pre-defined snapshot. Luckily, we don't have to set an actual client, since the tests and the implementation of the back-end live right next to each other, thus, we will install a package which will help us achieving so:
```sh
yarn add --dev apollo-server-testing
```
Define the test suite under the `tests/queries` folder in a file called `getChats.test.ts`:In the test function, we create a new instance of the Apollo-GraphQL server using our schema, and we query some data against it thanks to the fake client created by [apollo-server-testing](https://www.npmjs.com/package/apollo-server-testing).
The .toMatchSnapshot() matcher will call the toString() method on the examined object and will test it against a predefined snapshot. The snapshot will automatically be created once we run the test for the first time and will be stored under the __snapshot__ directory. This means that the first test run will always pass. This is useful because you can later on observe and adjust manually the snapshot manually without having to write it from scratch.
Do test run for the server: `yarn test`
```sh
~/Github/whatsapp-clone-server
❯ yarn test
yarn run v1.22.4
$ jest
PASS tests/queries/getChats.test.ts (9.35 s)
Query.chats
✓ should fetch all chats (38 ms)› 1 snapshot written.
Snapshot Summary
› 1 snapshot written from 1 test suite.Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 1 written, 1 total
Time: 11.508 s
Ran all test suites.
✨ Done in 14.45s.
```Open [tests/queries/__snapshots__/getChats.test.ts.snap](./tests/queries/__snapshots__/getChats.test.ts.snap) to see the produced snapshot.